// Copyright 2011-2023 David Robillard // SPDX-License-Identifier: ISC #include "node.h" #include "reader.h" #include "serd_internal.h" #include "stack.h" #include "string_utils.h" #include "try.h" #include "uri_utils.h" #include "serd/node.h" #include "serd/reader.h" #include "serd/statement.h" #include "serd/status.h" #include "serd/syntax.h" #include #include #include #include #include #if defined(__clang__) && __clang_major__ >= 10 # define SERD_FALLTHROUGH __attribute__((fallthrough)) _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wmissing-declarations\"") #elif defined(__GNUC__) && __GNUC__ >= 7 # define SERD_FALLTHROUGH __attribute__((fallthrough)) #else # define SERD_FALLTHROUGH #endif static bool fancy_syntax(const SerdReader* const reader) { return reader->syntax == SERD_TURTLE || reader->syntax == SERD_TRIG; } static SerdStatus read_collection(SerdReader* reader, ReadContext ctx, Ref* dest); static SerdStatus read_predicateObjectList(SerdReader* reader, ReadContext ctx, bool* ate_dot); static uint8_t read_HEX(SerdReader* const reader) { const int c = peek_byte(reader); if (is_xdigit(c)) { return (uint8_t)eat_byte_safe(reader, c); } r_err(reader, SERD_BAD_SYNTAX, "invalid hexadecimal digit '%c'\n", c); return 0; } // Read UCHAR escape, initial \ is already eaten by caller static SerdStatus read_UCHAR(SerdReader* const reader, const Ref dest, uint32_t* const char_code) { const int b = peek_byte(reader); unsigned length = 0; switch (b) { case 'U': length = 8; break; case 'u': length = 4; break; default: return SERD_BAD_SYNTAX; } skip_byte(reader, b); // Read character code point in hex uint8_t buf[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; uint32_t code = 0U; for (unsigned i = 0U; i < length; ++i) { if (!(buf[i] = read_HEX(reader))) { return SERD_BAD_SYNTAX; } code = (code << (i ? 4U : 0U)) | hex_digit_value(buf[i]); } // Determine the encoded size from the code point unsigned size = 0; if (code < 0x00000080) { size = 1; } else if (code < 0x00000800) { size = 2; } else if (code < 0x00010000) { size = 3; } else if (code < 0x00110000) { size = 4; } else { r_err( reader, SERD_BAD_SYNTAX, "unicode character 0x%X out of range\n", code); push_bytes(reader, dest, replacement_char, 3); *char_code = 0xFFFD; return SERD_SUCCESS; } // Build output in buf // (Note # of bytes = # of leading 1 bits in first byte) uint32_t c = code; switch (size) { case 4: buf[3] = (uint8_t)(0x80U | (c & 0x3FU)); c >>= 6; c |= (16 << 12); // set bit 4 SERD_FALLTHROUGH; case 3: buf[2] = (uint8_t)(0x80U | (c & 0x3FU)); c >>= 6; c |= (32 << 6); // set bit 5 SERD_FALLTHROUGH; case 2: buf[1] = (uint8_t)(0x80U | (c & 0x3FU)); c >>= 6; c |= 0xC0; // set bits 6 and 7 SERD_FALLTHROUGH; case 1: buf[0] = (uint8_t)c; SERD_FALLTHROUGH; default: break; } push_bytes(reader, dest, buf, size); *char_code = code; return SERD_SUCCESS; } // Read ECHAR escape, initial \ is already eaten by caller static SerdStatus read_ECHAR(SerdReader* const reader, const Ref dest, SerdNodeFlags* const flags) { SerdStatus st = SERD_SUCCESS; const int c = peek_byte(reader); switch (c) { case 't': return (st = skip_byte(reader, 't')) ? st : push_byte(reader, dest, '\t'); case 'b': return (st = skip_byte(reader, 'b')) ? st : push_byte(reader, dest, '\b'); case 'n': *flags |= SERD_HAS_NEWLINE; return (st = skip_byte(reader, 'n')) ? st : push_byte(reader, dest, '\n'); case 'r': *flags |= SERD_HAS_NEWLINE; return (st = skip_byte(reader, 'r')) ? st : push_byte(reader, dest, '\r'); case 'f': return (st = skip_byte(reader, 'f')) ? st : push_byte(reader, dest, '\f'); case '\\': case '"': case '\'': return push_byte(reader, dest, eat_byte_safe(reader, c)); default: return SERD_BAD_SYNTAX; } } static SerdStatus bad_char(SerdReader* const reader, const char* const fmt, const uint8_t c) { // Skip bytes until the next start byte for (int b = peek_byte(reader); b != EOF && ((uint8_t)b & 0x80);) { skip_byte(reader, b); b = peek_byte(reader); } r_err(reader, SERD_BAD_SYNTAX, fmt, c); return reader->strict ? SERD_BAD_SYNTAX : SERD_FAILURE; } static SerdStatus read_utf8_bytes(SerdReader* const reader, uint8_t bytes[4], uint32_t* const size, const uint8_t c) { *size = utf8_num_bytes(c); if (*size <= 1 || *size > 4) { return bad_char(reader, "invalid UTF-8 start 0x%X\n", c); } bytes[0] = c; for (unsigned i = 1; i < *size; ++i) { const int b = peek_byte(reader); if (b == EOF || ((uint8_t)b & 0x80) == 0) { return bad_char(reader, "invalid UTF-8 continuation 0x%X\n", (uint8_t)b); } bytes[i] = (uint8_t)eat_byte_safe(reader, b); } return SERD_SUCCESS; } static SerdStatus read_utf8_character(SerdReader* const reader, const Ref dest, const uint8_t c) { uint32_t size = 0; uint8_t bytes[4] = {0, 0, 0, 0}; SerdStatus st = read_utf8_bytes(reader, bytes, &size, c); if (st) { push_bytes(reader, dest, replacement_char, 3); } else { push_bytes(reader, dest, bytes, size); } return st; } static SerdStatus read_utf8_code(SerdReader* const reader, const Ref dest, uint32_t* const code, const uint8_t c) { uint32_t size = 0; uint8_t bytes[4] = {0, 0, 0, 0}; SerdStatus st = read_utf8_bytes(reader, bytes, &size, c); if (st) { push_bytes(reader, dest, replacement_char, 3); return st; } push_bytes(reader, dest, bytes, size); *code = parse_counted_utf8_char(bytes, size); return st; } // Read one character (possibly multi-byte) // The first byte, c, has already been eaten by caller static SerdStatus read_character(SerdReader* const reader, const Ref dest, SerdNodeFlags* const flags, const uint8_t c) { if (!(c & 0x80)) { switch (c) { case 0xA: case 0xD: *flags |= SERD_HAS_NEWLINE; break; case '"': case '\'': *flags |= SERD_HAS_QUOTE; break; default: break; } return push_byte(reader, dest, c); } return read_utf8_character(reader, dest, c); } // [10] comment ::= '#' ( [^#xA #xD] )* static void read_comment(SerdReader* const reader) { skip_byte(reader, '#'); int c = 0; while (((c = peek_byte(reader)) != 0xA) && c != 0xD && c != EOF && c) { skip_byte(reader, c); } } // [24] ws ::= #x9 | #xA | #xD | #x20 | comment static bool read_ws(SerdReader* const reader) { const int c = peek_byte(reader); switch (c) { case 0x9: case 0xA: case 0xD: case 0x20: skip_byte(reader, c); return true; case '#': read_comment(reader); return true; default: return false; } } static bool read_ws_star(SerdReader* const reader) { while (read_ws(reader)) { } return true; } static bool peek_delim(SerdReader* const reader, const uint8_t delim) { read_ws_star(reader); return peek_byte(reader) == delim; } static bool eat_delim(SerdReader* const reader, const uint8_t delim) { if (peek_delim(reader, delim)) { skip_byte(reader, delim); return read_ws_star(reader); } return false; } static SerdStatus read_string_escape(SerdReader* const reader, const Ref ref, SerdNodeFlags* const flags) { SerdStatus st = SERD_SUCCESS; uint32_t code = 0; if ((st = read_ECHAR(reader, ref, flags)) && (st = read_UCHAR(reader, ref, &code))) { return r_err(reader, st, "invalid escape '\\%c'\n", peek_byte(reader)); } return st; } // STRING_LITERAL_LONG_QUOTE and STRING_LITERAL_LONG_SINGLE_QUOTE // Initial triple quotes are already eaten by caller static SerdStatus read_STRING_LITERAL_LONG(SerdReader* const reader, const Ref ref, SerdNodeFlags* const flags, const uint8_t q) { SerdStatus st = SERD_SUCCESS; while (!(st && reader->strict)) { const int c = peek_byte(reader); if (c == '\\') { skip_byte(reader, c); st = read_string_escape(reader, ref, flags); } else if (c == q) { skip_byte(reader, q); const int q2 = eat_byte_safe(reader, peek_byte(reader)); const int q3 = peek_byte(reader); if (q2 == q && q3 == q) { // End of string skip_byte(reader, q3); break; } if (q2 == '\\') { push_byte(reader, ref, c); st = read_string_escape(reader, ref, flags); } else { *flags |= SERD_HAS_QUOTE; push_byte(reader, ref, c); st = read_character(reader, ref, flags, (uint8_t)q2); } } else if (c == EOF) { st = r_err(reader, SERD_BAD_SYNTAX, "end of file in long string\n"); } else { st = read_character(reader, ref, flags, (uint8_t)eat_byte_safe(reader, c)); } } return (st && reader->strict) ? st : SERD_SUCCESS; } // STRING_LITERAL_QUOTE and STRING_LITERAL_SINGLE_QUOTE // Initial quote is already eaten by caller static SerdStatus read_STRING_LITERAL(SerdReader* const reader, const Ref ref, SerdNodeFlags* const flags, const uint8_t q) { SerdStatus st = SERD_SUCCESS; while (!st || !reader->strict) { const int c = peek_byte(reader); switch (c) { case EOF: return r_err(reader, SERD_BAD_SYNTAX, "end of file in short string\n"); case '\n': case '\r': return r_err(reader, SERD_BAD_SYNTAX, "line end in short string\n"); case '\\': skip_byte(reader, c); TRY(st, read_string_escape(reader, ref, flags)); break; default: if (c == q) { return skip_byte(reader, c); } st = read_character(reader, ref, flags, (uint8_t)eat_byte_safe(reader, c)); } } return eat_byte_check(reader, q) ? SERD_SUCCESS : SERD_BAD_SYNTAX; } static SerdStatus read_String(SerdReader* const reader, const Ref node, SerdNodeFlags* const flags) { const int q1 = eat_byte_safe(reader, peek_byte(reader)); const int q2 = peek_byte(reader); if (q2 == EOF) { return r_err(reader, SERD_BAD_SYNTAX, "unexpected end of file\n"); } if (q2 != q1) { // Short string (not triple quoted) return read_STRING_LITERAL(reader, node, flags, (uint8_t)q1); } skip_byte(reader, q2); const int q3 = peek_byte(reader); if (q3 == EOF) { return r_err(reader, SERD_BAD_SYNTAX, "unexpected end of file\n"); } if (q3 != q1) { // Empty short string ("" or '') return SERD_SUCCESS; } if (!fancy_syntax(reader)) { return r_err( reader, SERD_BAD_SYNTAX, "syntax does not support long literals\n"); } skip_byte(reader, q3); return read_STRING_LITERAL_LONG(reader, node, flags, (uint8_t)q1); } static bool is_PN_CHARS_BASE(const uint32_t c) { return ((c >= 0x00C0 && c <= 0x00D6) || (c >= 0x00D8 && c <= 0x00F6) || (c >= 0x00F8 && c <= 0x02FF) || (c >= 0x0370 && c <= 0x037D) || (c >= 0x037F && c <= 0x1FFF) || (c >= 0x200C && c <= 0x200D) || (c >= 0x2070 && c <= 0x218F) || (c >= 0x2C00 && c <= 0x2FEF) || (c >= 0x3001 && c <= 0xD7FF) || (c >= 0xF900 && c <= 0xFDCF) || (c >= 0xFDF0 && c <= 0xFFFD) || (c >= 0x10000 && c <= 0xEFFFF)); } static SerdStatus read_PN_CHARS_BASE(SerdReader* const reader, const Ref dest) { uint32_t code = 0; const int c = peek_byte(reader); SerdStatus st = SERD_SUCCESS; if (is_alpha(c)) { return push_byte(reader, dest, eat_byte_safe(reader, c)); } if (c == EOF || !(c & 0x80)) { return SERD_FAILURE; } skip_byte(reader, c); read_utf8_code(reader, dest, &code, (uint8_t)c); if (!is_PN_CHARS_BASE(code)) { r_err(reader, SERD_BAD_SYNTAX, "invalid character U+%04X in name\n", code); if (reader->strict) { return SERD_BAD_SYNTAX; } } return st; } static bool is_PN_CHARS(const uint32_t c) { return (is_PN_CHARS_BASE(c) || c == 0xB7 || (c >= 0x0300 && c <= 0x036F) || (c >= 0x203F && c <= 0x2040)); } static SerdStatus read_PN_CHARS(SerdReader* const reader, const Ref dest) { uint32_t code = 0; const int c = peek_byte(reader); SerdStatus st = SERD_SUCCESS; if (is_alpha(c) || is_digit(c) || c == '_' || c == '-') { return push_byte(reader, dest, eat_byte_safe(reader, c)); } if (c == EOF || !(c & 0x80)) { return SERD_FAILURE; } skip_byte(reader, c); TRY(st, read_utf8_code(reader, dest, &code, (uint8_t)c)); if (!is_PN_CHARS(code)) { return r_err( reader, SERD_BAD_SYNTAX, "invalid character U+%04X in name\n", code); } return st; } static SerdStatus read_PERCENT(SerdReader* const reader, const Ref dest) { push_byte(reader, dest, eat_byte_safe(reader, '%')); const uint8_t h1 = read_HEX(reader); const uint8_t h2 = read_HEX(reader); if (h1 && h2) { push_byte(reader, dest, h1); return push_byte(reader, dest, h2); } return SERD_BAD_SYNTAX; } static SerdStatus read_PN_LOCAL_ESC(SerdReader* const reader, const Ref dest) { skip_byte(reader, '\\'); const int c = peek_byte(reader); switch (c) { case '!': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case ';': case '=': case '?': case '@': case '_': case '~': push_byte(reader, dest, eat_byte_safe(reader, c)); break; default: return r_err(reader, SERD_BAD_SYNTAX, "invalid escape\n"); } return SERD_SUCCESS; } static SerdStatus read_PLX(SerdReader* const reader, const Ref dest) { const int c = peek_byte(reader); switch (c) { case '%': return read_PERCENT(reader, dest); case '\\': return read_PN_LOCAL_ESC(reader, dest); default: return SERD_FAILURE; } } static SerdStatus read_PN_LOCAL(SerdReader* const reader, const Ref dest, bool* const ate_dot) { int c = peek_byte(reader); SerdStatus st = SERD_SUCCESS; bool trailing_unescaped_dot = false; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ':': case '_': push_byte(reader, dest, eat_byte_safe(reader, c)); break; default: if ((st = read_PLX(reader, dest)) > SERD_FAILURE) { return r_err(reader, st, "bad escape\n"); } if (st != SERD_SUCCESS && (st = read_PN_CHARS_BASE(reader, dest))) { return st; } } while ((c = peek_byte(reader))) { // Middle: (PN_CHARS | '.' | ':')* if (c == '.' || c == ':') { push_byte(reader, dest, eat_byte_safe(reader, c)); } else if ((st = read_PLX(reader, dest)) > SERD_FAILURE) { return r_err(reader, SERD_BAD_SYNTAX, "bad escape\n"); } else if (st != SERD_SUCCESS && (st = read_PN_CHARS(reader, dest))) { break; } trailing_unescaped_dot = (c == '.'); } SerdNode* const n = deref(reader, dest); if (trailing_unescaped_dot) { // Ate trailing dot, pop it from stack/node and inform caller --n->length; serd_stack_pop(&reader->stack, 1); *ate_dot = true; } return (st > SERD_FAILURE) ? st : SERD_SUCCESS; } // Read the remainder of a PN_PREFIX after some initial characters static SerdStatus read_PN_PREFIX_tail(SerdReader* const reader, const Ref dest) { int c = 0; while ((c = peek_byte(reader))) { // Middle: (PN_CHARS | '.')* if (c == '.') { push_byte(reader, dest, eat_byte_safe(reader, c)); } else if (read_PN_CHARS(reader, dest)) { break; } } const SerdNode* const n = deref(reader, dest); if (serd_node_string(n)[serd_node_length(n) - 1] == '.' && read_PN_CHARS(reader, dest)) { return r_err(reader, SERD_BAD_SYNTAX, "prefix ends with '.'\n"); } return SERD_SUCCESS; } static SerdStatus read_PN_PREFIX(SerdReader* const reader, const Ref dest) { const SerdStatus st = read_PN_CHARS_BASE(reader, dest); return st ? st : read_PN_PREFIX_tail(reader, dest); } static SerdStatus read_LANGTAG(SerdReader* const reader, Ref* const dest) { int c = peek_byte(reader); if (!is_alpha(c)) { return r_err(reader, SERD_BAD_SYNTAX, "unexpected '%c'\n", c); } *dest = push_node(reader, SERD_LITERAL, "", 0); SerdStatus st = SERD_SUCCESS; TRY(st, push_byte(reader, *dest, eat_byte_safe(reader, c))); while ((c = peek_byte(reader)) && is_alpha(c)) { TRY(st, push_byte(reader, *dest, eat_byte_safe(reader, c))); } while (peek_byte(reader) == '-') { TRY(st, push_byte(reader, *dest, eat_byte_safe(reader, '-'))); while ((c = peek_byte(reader)) && (is_alpha(c) || is_digit(c))) { TRY(st, push_byte(reader, *dest, eat_byte_safe(reader, c))); } } return SERD_SUCCESS; } static SerdStatus read_IRIREF_scheme(SerdReader* const reader, const Ref dest) { int c = peek_byte(reader); if (!is_alpha(c)) { return r_err(reader, SERD_BAD_SYNTAX, "bad IRI scheme start '%c'\n", c); } while ((c = peek_byte(reader)) != EOF) { if (c == '>') { return r_err(reader, SERD_BAD_SYNTAX, "missing IRI scheme\n"); } if (!is_uri_scheme_char(c)) { return r_err(reader, SERD_BAD_SYNTAX, "bad IRI scheme char U+%04X (%c)\n", (unsigned)c, (char)c); } push_byte(reader, dest, eat_byte_safe(reader, c)); if (c == ':') { return SERD_SUCCESS; // End of scheme } } return r_err(reader, SERD_BAD_SYNTAX, "unexpected end of file\n"); } static SerdStatus read_IRIREF(SerdReader* const reader, Ref* const dest) { if (!eat_byte_check(reader, '<')) { return SERD_BAD_SYNTAX; } *dest = push_node(reader, SERD_URI, "", 0); if (!fancy_syntax(reader) && read_IRIREF_scheme(reader, *dest)) { *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "expected IRI scheme\n"); } SerdStatus st = SERD_SUCCESS; uint32_t code = 0; while (st <= SERD_FAILURE) { const int c = eat_byte_safe(reader, peek_byte(reader)); switch (c) { case '"': case '<': *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "invalid IRI character '%c'\n", c); case '>': return SERD_SUCCESS; case '\\': if (read_UCHAR(reader, *dest, &code)) { *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "invalid IRI escape\n"); } switch (code) { case 0: case ' ': case '<': case '>': *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "invalid escaped IRI character U+%04X\n", code); default: break; } break; case '^': case '`': case '{': case '|': case '}': *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "invalid IRI character '%c'\n", c); default: if (c <= 0x20) { st = r_err(reader, SERD_BAD_SYNTAX, "invalid IRI character (escape %%%02X)\n", (unsigned)c); if (reader->strict) { break; } st = SERD_FAILURE; push_byte(reader, *dest, c); } else if (!(c & 0x80)) { push_byte(reader, *dest, c); } else if (read_utf8_character(reader, *dest, (uint8_t)c)) { if (reader->strict) { *dest = pop_node(reader, *dest); return SERD_BAD_SYNTAX; } } } } *dest = pop_node(reader, *dest); return st; } static SerdStatus read_PrefixedName(SerdReader* const reader, const Ref dest, const bool read_prefix, bool* const ate_dot) { SerdStatus st = SERD_SUCCESS; if (read_prefix) { TRY_FAILING(st, read_PN_PREFIX(reader, dest)); } if (peek_byte(reader) != ':') { return SERD_FAILURE; } push_byte(reader, dest, eat_byte_safe(reader, ':')); TRY_FAILING(st, read_PN_LOCAL(reader, dest, ate_dot)); return SERD_SUCCESS; } static SerdStatus read_0_9(SerdReader* const reader, const Ref str, const bool at_least_one) { unsigned count = 0; SerdStatus st = SERD_SUCCESS; for (int c = 0; is_digit((c = peek_byte(reader))); ++count) { TRY(st, push_byte(reader, str, eat_byte_safe(reader, c))); } if (at_least_one && count == 0) { return r_err(reader, SERD_BAD_SYNTAX, "expected digit\n"); } return SERD_SUCCESS; } static SerdStatus read_number(SerdReader* const reader, Ref* const dest, Ref* const datatype, SerdNodeFlags* const flags, bool* const ate_dot) { #define XSD_DECIMAL NS_XSD "decimal" #define XSD_DOUBLE NS_XSD "double" #define XSD_INTEGER NS_XSD "integer" *dest = push_node(reader, SERD_LITERAL, "", 0); SerdStatus st = SERD_SUCCESS; int c = peek_byte(reader); bool has_decimal = false; if (c == '-' || c == '+') { push_byte(reader, *dest, eat_byte_safe(reader, c)); } if ((c = peek_byte(reader)) == '.') { has_decimal = true; // decimal case 2 (e.g. '.0' or '-.0' or '+.0') push_byte(reader, *dest, eat_byte_safe(reader, c)); TRY(st, read_0_9(reader, *dest, true)); } else { // all other cases ::= ( '-' | '+' ) [0-9]+ ( . )? ( [0-9]+ )? ... TRY(st, read_0_9(reader, *dest, true)); if ((c = peek_byte(reader)) == '.') { has_decimal = true; // Annoyingly, dot can be end of statement, so tentatively eat skip_byte(reader, c); c = peek_byte(reader); if (!is_digit(c) && c != 'e' && c != 'E') { *ate_dot = true; // Force caller to deal with stupid grammar return SERD_SUCCESS; // Next byte is not a number character } push_byte(reader, *dest, '.'); read_0_9(reader, *dest, false); } } c = peek_byte(reader); if (c == 'e' || c == 'E') { // double push_byte(reader, *dest, eat_byte_safe(reader, c)); switch ((c = peek_byte(reader))) { case '+': case '-': push_byte(reader, *dest, eat_byte_safe(reader, c)); break; default: break; } TRY(st, read_0_9(reader, *dest, true)); *datatype = push_node(reader, SERD_URI, XSD_DOUBLE, sizeof(XSD_DOUBLE) - 1); } else if (has_decimal) { *datatype = push_node(reader, SERD_URI, XSD_DECIMAL, sizeof(XSD_DECIMAL) - 1); } else { *datatype = push_node(reader, SERD_URI, XSD_INTEGER, sizeof(XSD_INTEGER) - 1); } if (*datatype) { *flags |= SERD_HAS_DATATYPE; } return SERD_SUCCESS; } static SerdStatus read_iri(SerdReader* const reader, Ref* const dest, bool* const ate_dot) { switch (peek_byte(reader)) { case '<': return read_IRIREF(reader, dest); default: *dest = push_node(reader, SERD_CURIE, "", 0); return read_PrefixedName(reader, *dest, true, ate_dot); } } static SerdStatus read_literal(SerdReader* const reader, Ref* const dest, Ref* const datatype, Ref* const lang, SerdNodeFlags* const flags, bool* const ate_dot) { *dest = push_node(reader, SERD_LITERAL, "", 0); SerdStatus st = read_String(reader, *dest, flags); if (st) { *dest = pop_node(reader, *dest); return st; } switch (peek_byte(reader)) { case '@': skip_byte(reader, '@'); *flags |= SERD_HAS_LANGUAGE; if ((st = read_LANGTAG(reader, lang))) { *datatype = pop_node(reader, *datatype); *lang = pop_node(reader, *lang); *dest = pop_node(reader, *dest); return r_err(reader, st, "bad language tag\n"); } break; case '^': skip_byte(reader, '^'); if (!eat_byte_check(reader, '^')) { return r_err(reader, SERD_BAD_SYNTAX, "expected '^'\n"); } *flags |= SERD_HAS_DATATYPE; if ((st = read_iri(reader, datatype, ate_dot))) { *datatype = pop_node(reader, *datatype); *lang = pop_node(reader, *lang); *dest = pop_node(reader, *dest); return r_err(reader, st, "bad datatype\n"); } break; } return SERD_SUCCESS; } static SerdStatus read_verb(SerdReader* const reader, Ref* const dest) { if (peek_byte(reader) == '<') { return read_IRIREF(reader, dest); } /* Either a qname, or "a". Read the prefix first, and if it is in fact "a", produce that instead. */ *dest = push_node(reader, SERD_CURIE, "", 0); SerdStatus st = read_PN_PREFIX(reader, *dest); bool ate_dot = false; SerdNode* node = deref(reader, *dest); const int next = peek_byte(reader); if (!st && node->length == 1 && serd_node_string(node)[0] == 'a' && next != ':' && !is_PN_CHARS_BASE((uint32_t)next)) { pop_node(reader, *dest); *dest = push_node(reader, SERD_URI, NS_RDF "type", 47); return SERD_SUCCESS; } if (st > SERD_FAILURE || (st = read_PrefixedName(reader, *dest, false, &ate_dot)) || ate_dot) { *dest = pop_node(reader, *dest); st = st > SERD_FAILURE ? st : SERD_BAD_SYNTAX; return r_err(reader, st, "bad verb\n"); } return SERD_SUCCESS; } static SerdStatus read_BLANK_NODE_LABEL(SerdReader* const reader, Ref* const dest, bool* const ate_dot) { skip_byte(reader, '_'); if (!eat_byte_check(reader, ':')) { return SERD_BAD_SYNTAX; } const Ref ref = *dest = push_node(reader, SERD_BLANK, reader->bprefix ? reader->bprefix : "", reader->bprefix_len); int c = peek_byte(reader); // First: (PN_CHARS | '_' | [0-9]) if (is_digit(c) || c == '_') { push_byte(reader, ref, eat_byte_safe(reader, c)); } else if (read_PN_CHARS(reader, ref)) { *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "invalid name start\n"); } while ((c = peek_byte(reader))) { // Middle: (PN_CHARS | '.')* if (c == '.') { push_byte(reader, ref, eat_byte_safe(reader, c)); } else if (read_PN_CHARS(reader, ref)) { break; } } SerdNode* n = deref(reader, ref); char* buf = serd_node_buffer(n); if (buf[n->length - 1] == '.' && read_PN_CHARS(reader, ref)) { // Ate trailing dot, pop it from stack/node and inform caller --n->length; serd_stack_pop(&reader->stack, 1); *ate_dot = true; } if (fancy_syntax(reader)) { if (is_digit(buf[reader->bprefix_len + 1])) { if ((buf[reader->bprefix_len]) == 'b') { buf[reader->bprefix_len] = 'B'; // Prevent clash reader->seen_genid = true; } else if (reader->seen_genid && buf[reader->bprefix_len] == 'B') { *dest = pop_node(reader, *dest); return r_err(reader, SERD_BAD_LABEL, "found both 'b' and 'B' blank IDs, prefix required\n"); } } } return SERD_SUCCESS; } static SerdStatus read_anon(SerdReader* const reader, ReadContext ctx, const bool subject, Ref* const dest) { skip_byte(reader, '['); 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 |= empty ? SERD_EMPTY_O : SERD_ANON_O_BEGIN; } if (!*dest) { *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)); } // 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; TRY_FAILING(st, read_predicateObjectList(reader, ctx, &ate_dot_in_list)); if (ate_dot_in_list) { return r_err(reader, SERD_BAD_SYNTAX, "'.' inside blank\n"); } read_ws_star(reader); if (reader->end_func) { reader->end_func(reader->handle, deref(reader, *dest)); } *ctx.flags = old_flags; } return st > SERD_FAILURE ? st : (eat_byte_check(reader, ']') == ']') ? SERD_SUCCESS : SERD_BAD_SYNTAX; } /* If emit is true: recurses, calling statement_sink for every statement encountered, and leaves stack in original calling state (i.e. pops everything it pushes). */ static SerdStatus read_object(SerdReader* const reader, ReadContext* const ctx, const bool emit, bool* const ate_dot) { static const char* const XSD_BOOLEAN = NS_XSD "boolean"; static const size_t XSD_BOOLEAN_LEN = 40; #ifndef NDEBUG const size_t orig_stack_size = reader->stack.size; #endif SerdStatus st = SERD_FAILURE; bool simple = (ctx->subject != 0); SerdNode* node = NULL; Ref o = 0; Ref datatype = 0; Ref lang = 0; uint32_t flags = 0; const int c = peek_byte(reader); if (!fancy_syntax(reader)) { switch (c) { case '"': case ':': case '<': case '_': break; default: return r_err(reader, SERD_BAD_SYNTAX, "expected: ':', '<', or '_'\n"); } } switch (c) { case EOF: case ')': return r_err(reader, SERD_BAD_SYNTAX, "expected object\n"); case '[': simple = false; st = read_anon(reader, *ctx, false, &o); break; case '(': simple = false; st = read_collection(reader, *ctx, &o); break; case '_': st = read_BLANK_NODE_LABEL(reader, &o, ate_dot); break; case '<': case ':': st = read_iri(reader, &o, ate_dot); break; case '+': case '-': case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': st = read_number(reader, &o, &datatype, &flags, ate_dot); break; case '\"': case '\'': st = read_literal(reader, &o, &datatype, &lang, &flags, ate_dot); break; default: /* Either a boolean literal, or a qname. Read the prefix first, and if it is in fact a "true" or "false" literal, produce that instead. */ o = push_node(reader, SERD_CURIE, "", 0); while (!read_PN_CHARS_BASE(reader, o)) { } node = deref(reader, o); if ((node->length == 4 && !memcmp(serd_node_string(node), "true", 4)) || (node->length == 5 && !memcmp(serd_node_string(node), "false", 5))) { flags = flags | SERD_HAS_DATATYPE; node->type = SERD_LITERAL; datatype = push_node(reader, SERD_URI, XSD_BOOLEAN, XSD_BOOLEAN_LEN); st = SERD_SUCCESS; } else if (read_PN_PREFIX_tail(reader, o) > SERD_FAILURE) { st = SERD_BAD_SYNTAX; } else { if ((st = read_PrefixedName(reader, o, false, ate_dot))) { st = st > SERD_FAILURE ? st : SERD_BAD_SYNTAX; pop_node(reader, o); return r_err(reader, st, "expected prefixed name\n"); } } } if (!st && simple && o) { deref(reader, o)->flags = flags; } if (!st && emit && simple) { st = emit_statement(reader, *ctx, o); } else if (!st && !emit) { ctx->object = o; ctx->datatype = datatype; ctx->lang = lang; return SERD_SUCCESS; } pop_node(reader, lang); pop_node(reader, datatype); pop_node(reader, o); #ifndef NDEBUG assert(reader->stack.size == orig_stack_size); #endif return st; } static SerdStatus read_objectList(SerdReader* const reader, ReadContext ctx, bool* const ate_dot) { SerdStatus st = SERD_SUCCESS; TRY(st, read_object(reader, &ctx, true, ate_dot)); if (!fancy_syntax(reader) && peek_delim(reader, ',')) { return r_err( reader, SERD_BAD_SYNTAX, "syntax does not support abbreviation\n"); } while (!*ate_dot && eat_delim(reader, ',')) { st = read_object(reader, &ctx, true, ate_dot); } return st; } static SerdStatus read_predicateObjectList(SerdReader* const reader, ReadContext ctx, bool* const ate_dot) { SerdStatus st = SERD_SUCCESS; while (!(st = read_verb(reader, &ctx.predicate)) && read_ws_star(reader) && !(st = read_objectList(reader, ctx, ate_dot))) { ctx.predicate = pop_node(reader, ctx.predicate); if (*ate_dot) { return SERD_SUCCESS; } bool ate_semi = false; int c = 0; do { read_ws_star(reader); switch (c = peek_byte(reader)) { case EOF: return r_err(reader, SERD_BAD_SYNTAX, "unexpected end of file\n"); case '.': case ']': case '}': return SERD_SUCCESS; case ';': skip_byte(reader, c); ate_semi = true; } } while (c == ';'); if (!ate_semi) { return r_err(reader, SERD_BAD_SYNTAX, "missing ';' or '.'\n"); } } ctx.predicate = pop_node(reader, ctx.predicate); return st; } static SerdStatus end_collection(SerdReader* const reader, const ReadContext ctx, const Ref n1, const Ref n2, const SerdStatus st) { pop_node(reader, n2); pop_node(reader, n1); *ctx.flags &= ~(unsigned)SERD_LIST_CONT; if (!st) { return (eat_byte_check(reader, ')') == ')') ? SERD_SUCCESS : SERD_BAD_SYNTAX; } return st; } static SerdStatus read_collection(SerdReader* const reader, ReadContext ctx, Ref* const dest) { SerdStatus st = SERD_SUCCESS; skip_byte(reader, '('); bool end = peek_delim(reader, ')'); *dest = end ? reader->rdf_nil : blank_id(reader); if (ctx.subject) { // Reading a collection object *ctx.flags |= (end ? 0 : SERD_LIST_O_BEGIN); TRY(st, emit_statement(reader, ctx, *dest)); *ctx.flags &= SERD_LIST_O_BEGIN; *ctx.flags |= SERD_LIST_CONT; } else { // Reading a collection subject *ctx.flags |= (end ? 0 : SERD_LIST_S_BEGIN); } if (end) { return end_collection(reader, ctx, 0, 0, st); } /* The order of node allocation here is necessarily not in stack order, so we create two nodes and recycle them throughout. */ Ref n1 = push_node_padded(reader, genid_size(reader), SERD_BLANK, "", 0); Ref n2 = 0; Ref node = n1; Ref rest = 0; ctx.subject = *dest; while (!peek_delim(reader, ')')) { // _:node rdf:first object 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, n1, n2, st); } if (!(end = peek_delim(reader, ')'))) { /* Give rest a new ID. Done as late as possible to ensure it is used and > IDs generated by read_object above. */ if (!rest) { rest = n2 = blank_id(reader); // First pass, push } else { set_blank_id(reader, rest, genid_size(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))); ctx.subject = rest; // _:node = _:rest rest = node; // _:rest = (old)_:node node = ctx.subject; // invariant } return end_collection(reader, ctx, n1, n2, st); } static SerdStatus read_subject(SerdReader* const reader, const ReadContext ctx, Ref* const dest, int* const s_type) { SerdStatus st = SERD_SUCCESS; bool ate_dot = false; switch ((*s_type = peek_byte(reader))) { case '[': st = read_anon(reader, ctx, true, dest); break; case '(': st = read_collection(reader, ctx, dest); break; case '_': st = read_BLANK_NODE_LABEL(reader, dest, &ate_dot); break; default: st = read_iri(reader, dest, &ate_dot); } if (ate_dot) { pop_node(reader, *dest); return r_err(reader, SERD_BAD_SYNTAX, "subject ends with '.'\n"); } return st; } static SerdStatus read_labelOrSubject(SerdReader* const reader, Ref* const dest) { bool ate_dot = false; switch (peek_byte(reader)) { case '[': skip_byte(reader, '['); read_ws_star(reader); if (!eat_byte_check(reader, ']')) { return SERD_BAD_SYNTAX; } *dest = blank_id(reader); return SERD_SUCCESS; case '_': return read_BLANK_NODE_LABEL(reader, dest, &ate_dot); default: if (!read_iri(reader, dest, &ate_dot)) { return SERD_SUCCESS; } else { return r_err(reader, SERD_BAD_SYNTAX, "expected label or subject\n"); } } } static SerdStatus read_triples(SerdReader* const reader, ReadContext ctx, bool* const ate_dot) { SerdStatus st = SERD_FAILURE; if (ctx.subject) { read_ws_star(reader); switch (peek_byte(reader)) { case '.': *ate_dot = eat_byte_safe(reader, '.'); return SERD_FAILURE; case '}': return SERD_FAILURE; } st = read_predicateObjectList(reader, ctx, ate_dot); } ctx.subject = ctx.predicate = 0; return st > SERD_FAILURE ? st : SERD_SUCCESS; } static SerdStatus read_base(SerdReader* const reader, const bool sparql, const bool token) { SerdStatus st = SERD_SUCCESS; if (token) { TRY(st, eat_string(reader, "base", 4)); } read_ws_star(reader); Ref uri = 0; TRY(st, read_IRIREF(reader, &uri)); if (reader->base_func) { TRY(st, reader->base_func(reader->handle, deref(reader, uri))); } pop_node(reader, uri); read_ws_star(reader); if (!sparql) { return eat_byte_check(reader, '.') ? SERD_SUCCESS : SERD_BAD_SYNTAX; } if (peek_byte(reader) == '.') { return r_err(reader, SERD_BAD_SYNTAX, "full stop after SPARQL BASE\n"); } return SERD_SUCCESS; } static SerdStatus read_prefixID(SerdReader* const reader, const bool sparql, const bool token) { SerdStatus st = SERD_SUCCESS; if (token) { TRY(st, eat_string(reader, "prefix", 6)); } read_ws_star(reader); Ref name = push_node(reader, SERD_LITERAL, "", 0); TRY_FAILING(st, read_PN_PREFIX(reader, name)); if (eat_byte_check(reader, ':') != ':') { pop_node(reader, name); return SERD_BAD_SYNTAX; } read_ws_star(reader); Ref uri = 0; TRY(st, read_IRIREF(reader, &uri)); if (reader->prefix_func) { st = reader->prefix_func( reader->handle, deref(reader, name), deref(reader, uri)); } pop_node(reader, uri); pop_node(reader, name); if (!sparql) { read_ws_star(reader); st = eat_byte_check(reader, '.') ? SERD_SUCCESS : SERD_BAD_SYNTAX; } return st; } static SerdStatus read_directive(SerdReader* const reader) { const bool sparql = peek_byte(reader) != '@'; if (!sparql) { skip_byte(reader, '@'); switch (peek_byte(reader)) { case 'B': case 'P': return r_err(reader, SERD_BAD_SYNTAX, "uppercase directive\n"); } } switch (peek_byte(reader)) { case 'B': case 'b': return read_base(reader, sparql, true); case 'P': case 'p': return read_prefixID(reader, sparql, true); default: break; } return r_err(reader, SERD_BAD_SYNTAX, "invalid directive\n"); } static SerdStatus read_wrappedGraph(SerdReader* const reader, ReadContext* const ctx) { if (!eat_byte_check(reader, '{')) { return SERD_BAD_SYNTAX; } read_ws_star(reader); while (peek_byte(reader) != '}') { bool ate_dot = false; int s_type = 0; ctx->subject = 0; SerdStatus st = read_subject(reader, *ctx, &ctx->subject, &s_type); if (st) { return r_err(reader, SERD_BAD_SYNTAX, "bad subject\n"); } if ((st = read_triples(reader, *ctx, &ate_dot)) && s_type != '[') { return r_err(reader, st, "bad predicate object list\n"); } ctx->subject = pop_node(reader, ctx->subject); read_ws_star(reader); if (peek_byte(reader) == '.') { skip_byte(reader, '.'); } read_ws_star(reader); } skip_byte(reader, '}'); read_ws_star(reader); if (peek_byte(reader) == '.') { return r_err(reader, SERD_BAD_SYNTAX, "graph followed by '.'\n"); } return SERD_SUCCESS; } static int tokcmp(SerdReader* const reader, const Ref ref, const char* const tok, const size_t n) { SerdNode* node = deref(reader, ref); if (!node || node->length != n) { return -1; } return serd_strncasecmp(serd_node_string(node), tok, n); } SerdStatus read_n3_statement(SerdReader* const reader) { #ifndef NDEBUG const size_t orig_stack_size = reader->stack.size; #endif SerdStatementFlags flags = 0; ReadContext ctx = {0, 0, 0, 0, 0, 0, &flags}; bool ate_dot = false; int s_type = 0; SerdStatus st = SERD_SUCCESS; read_ws_star(reader); switch (peek_byte(reader)) { case '\0': skip_byte(reader, '\0'); return SERD_FAILURE; case EOF: return SERD_FAILURE; case '@': if (!fancy_syntax(reader)) { return r_err( reader, SERD_BAD_SYNTAX, "syntax does not support directives\n"); } TRY(st, read_directive(reader)); read_ws_star(reader); break; case '{': if (reader->syntax == SERD_TRIG) { TRY(st, read_wrappedGraph(reader, &ctx)); read_ws_star(reader); } else { return r_err(reader, SERD_BAD_SYNTAX, "syntax does not support graphs\n"); } break; default: TRY_FAILING(st, read_subject(reader, ctx, &ctx.subject, &s_type)); if (!tokcmp(reader, ctx.subject, "base", 4)) { st = read_base(reader, true, false); } else if (!tokcmp(reader, ctx.subject, "prefix", 6)) { st = read_prefixID(reader, true, false); } else if (!tokcmp(reader, ctx.subject, "graph", 5)) { ctx.subject = pop_node(reader, ctx.subject); read_ws_star(reader); TRY(st, read_labelOrSubject(reader, &ctx.graph)); read_ws_star(reader); TRY(st, read_wrappedGraph(reader, &ctx)); pop_node(reader, ctx.graph); ctx.graph = 0; read_ws_star(reader); } else if (!tokcmp(reader, ctx.subject, "true", 4) || !tokcmp(reader, ctx.subject, "false", 5)) { return r_err(reader, SERD_BAD_SYNTAX, "expected subject\n"); } else if (read_ws_star(reader) && peek_byte(reader) == '{') { if (s_type == '(' || (s_type == '[' && !*ctx.flags)) { return r_err(reader, SERD_BAD_SYNTAX, "invalid graph name\n"); } ctx.graph = ctx.subject; ctx.subject = 0; TRY(st, read_wrappedGraph(reader, &ctx)); pop_node(reader, ctx.graph); read_ws_star(reader); } else if ((st = read_triples(reader, ctx, &ate_dot))) { if (st == SERD_FAILURE && s_type == '[') { return SERD_SUCCESS; } if (ate_dot) { return r_err(reader, SERD_BAD_SYNTAX, "unexpected end of statement\n"); } return st > SERD_FAILURE ? st : SERD_BAD_SYNTAX; } else if (!ate_dot) { read_ws_star(reader); st = (eat_byte_check(reader, '.') == '.') ? SERD_SUCCESS : SERD_BAD_SYNTAX; } ctx.subject = pop_node(reader, ctx.subject); break; } #ifndef NDEBUG assert(reader->stack.size == orig_stack_size); #endif return st; } SerdStatus serd_reader_skip_until_byte(SerdReader* const reader, const uint8_t byte) { int c = peek_byte(reader); while (c != byte && c != EOF) { skip_byte(reader, c); c = peek_byte(reader); } return c == EOF ? SERD_FAILURE : SERD_SUCCESS; } SerdStatus read_turtleTrigDoc(SerdReader* const reader) { while (!reader->source.eof) { const SerdStatus st = read_n3_statement(reader); if (st > SERD_FAILURE) { if (reader->strict) { return st; } serd_reader_skip_until_byte(reader, '\n'); } } return SERD_SUCCESS; } SerdStatus read_nquads_statement(SerdReader* const reader) { SerdStatus st = SERD_SUCCESS; SerdStatementFlags flags = 0; ReadContext ctx = {0, 0, 0, 0, 0, 0, &flags}; bool ate_dot = false; int s_type = 0; read_ws_star(reader); if (peek_byte(reader) == EOF) { return SERD_FAILURE; } if (peek_byte(reader) == '@') { return r_err( reader, SERD_BAD_SYNTAX, "syntax does not support directives\n"); } // subject predicate object if ((st = read_subject(reader, ctx, &ctx.subject, &s_type)) || !read_ws_star(reader) || (st = read_IRIREF(reader, &ctx.predicate)) || !read_ws_star(reader) || (st = read_object(reader, &ctx, false, &ate_dot))) { return st; } if (!ate_dot) { // graphLabel? read_ws_star(reader); switch (peek_byte(reader)) { case '.': break; case '_': TRY(st, read_BLANK_NODE_LABEL(reader, &ctx.graph, &ate_dot)); break; default: TRY(st, read_IRIREF(reader, &ctx.graph)); } // Terminating '.' read_ws_star(reader); if (!eat_byte_check(reader, '.')) { return SERD_BAD_SYNTAX; } } TRY(st, emit_statement(reader, ctx, ctx.object)); pop_node(reader, ctx.graph); pop_node(reader, ctx.lang); pop_node(reader, ctx.datatype); pop_node(reader, ctx.object); return SERD_SUCCESS; } SerdStatus read_nquadsDoc(SerdReader* const reader) { SerdStatus st = SERD_SUCCESS; while (!reader->source.eof && !st) { st = read_nquads_statement(reader); } return st; } #if defined(__clang__) && __clang_major__ >= 10 _Pragma("clang diagnostic pop") #endif