From ee081ca70322cd01d0642fdb1860e6bfbfb48e85 Mon Sep 17 00:00:00 2001 From: steven-vd Date: Sun, 16 Aug 2026 15:00:32 +0200 Subject: Initial commit --- imxml.h | 1934 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1934 insertions(+) create mode 100644 imxml.h (limited to 'imxml.h') diff --git a/imxml.h b/imxml.h new file mode 100644 index 0000000..7ed0563 --- /dev/null +++ b/imxml.h @@ -0,0 +1,1934 @@ +#ifndef IMXML_H_ +#define IMXML_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* TODO + - a nice way of handling entities inside attribute values. Maybe generalize xml_content + - recursive entity references + - normalization + - end-of-line normalization (CR and CRLF to LF) + - trim whitespace + - attribute-value whitespace normalization + - collapse whitespace + + MAYBE/IDEAS + - signal handling helper + - DTD validation helper functions, maybe? + - XPath + - SYSTEM entities + - ESP port + - XSLT + - schema to code transpiler +*/ + +/* ===== DOCUMENTATION ===== + * === EXPERIMENTAL === + * This library is still in an experimental stage and should not be used in + * environments where stability/safety/security/reliability are important. + * Some minor features like normalization and recursive entity references + * aren't supported yet. + * + * While this library is in an experimental state, semantic versioning + * conventions will not be respected and minor updates may introduce breaking + * API/ABI changes. Semantic versioning will be respected once this library + * hits 1.0. + * + * === COMPATIBILITY === + * Standard C89 (ANSI C) and C++98 are supported. The library has been tested with + * clang, gcc, and tcc. Every standards compliant C compiler should work, + * though if emmintrin.h/immintrin.h isn't supported, only SCALAR will work + * (unless you define your own instruction set). + * + * UTF-8, ISO-Latin-1, ASCII, and in general all char-based encodings are + * supported. UTF-16 is not supported. + * + * === USAGE === + * This API is intended to be used as part of a while (true) loop where you + * break when `xml_tag() == "/your-root"`. For specific examples, see the + * examples/ and benchmarks/imxml/ directories. Here is a basic example for + * counting the number of highway crossing nodes are in a OSM file: + +#define IMXML_LINUX // We're using Linux, of course +#define IMXML_NO_SUPPORT_SINGLE_QUOTES // OSM always uses double-quotes +#define IMXML_NO_SUPPORT_COMMENTS // OSM doesn't include comments +#define IMXML_NO_SUPPORT_CDATA // OSM doesn't include CDATA +#define IMXML_NO_SUPPORT_ENTITIES // We don't need to parse entities +#define IMXML_NO_SUPPORT_NAMESPACES // OSM doesn't include namespaces +#define IMXML_NO_CHECK_BOUNDS // We know our data will end with +#define THREADLOCAL // We disable thread-local by defining it as nothing +#define IMXML_IMPLEMENTATION +#include "imxml.h" +#include // for printf + +int main (void) { + size_t file_size; + // We need to use this file_open function because the input data needs to + // be padded to make sure we're not reading out-of-bounds + char* const file = imxml_linux_file_open("dat/yellowstone.osm", &file_size, false); + if (file == 0) return -1; + XmlParser p = {0}; + p.head = file; + + // Verify we're really dealing with osm data. This is mostly unnecessary, + // but it doesn't cost much and might catch some dumb bugs + if (!xml_parse_header(&p, NULL)) goto err; + if (!xml_tag_expect(&p, "osm")) goto err; + if (!xml_has_children(&p)) goto err; + if (!xml_tag_expect(&p, "bounds")) goto err; + if (xml_has_children(&p)) goto err; + + size_t highway_crossing_count = 0; + while (true) { + ImxmlString tag = xml_tag(&p); + if (imxml_streql(tag, imxml_strlit("node"))) { + if (xml_has_children(&p)) { + while (true) { + tag = xml_tag(&p); + if (imxml_streql(tag, imxml_strlit("/node"))) break; + ImxmlString key = xml_value(&p); + if (!imxml_streql(key, imxml_strlit("highway"))) continue; + ImxmlString value = xml_value(&p); + if (imxml_streql(value, imxml_strlit("crossing"))) { + highway_crossing_count += 1; + } + } + } + } else { + // encountered non-node tag, which means we can early-out (because + // OSM always groups all of the nodes up-front). + // Alternatively, we could check if tag == "/osm", but then we'd + // just be skipping over all the and tags, which + // would be a waste of time + break; + } + } + + printf("%zu\n", highway_crossing_count); + imxml_linux_file_close(file, file_size); + return 0; + + err: + return -1; +} + + * + * === PERFORMANCE FLAGS AND #DEFINES === + * These flags each measurably improve performance in at least some situations, + * at the cost of runtime safety and/or standards compliance. Most of these + * will only give you a few percent better performance, with the exception of + * IMXML_NO_SUPPORT_NAMESPACES and IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD, which + * can impact parse-times by about 30%. It's highly recommended to disable + * namespace support entirely, since it's usually not necessary. + * IMXML_NO_CHECK_BOUNDS + * If set, the parser skips checks for NUL terminators. It's + * recommended to enable this only if you can guarantee that the XML will be + * valid. If you can guarantee valid XML, set this flag and break on `xml_tag + * == "/root-node"` to improve performance. Even if you can't guarantee valid + * XML, consider handling SIGSEGV and SIGBUS instead of enabling bounds + * checking to improve performance. + * IMXML_NO_SUPPORT_COMMENTS + * If set, the parser assumes will not occur in the + * input. Only set this flag if you can guarantee that your XML contains no + * comments. If a comment is encountered when this flag is set, the parser + * will break in unpredictable ways. + * IMXML_NO_SUPPORT_CDATA + * If set, the parser assumes will not occur in the + * input. Only set this flag if you can guarantee that your XML contains no + * CDATA. If CDATA is encountered when this flag is set, the parser will + * break in unpredictable ways. + * IMXML_NO_SUPPORT_SINGLE_QUOTES + * If set, the parser assumes no attribute values (except the XML in the + * header) will be enclosed by 'single quotes'. This flag is relevant even if + * you don't read the attribute values, only disable it if you can guarantee + * there are either no attribute values in the XML, or all attribute values + * are enclosed in "double quotes". This flag is usually mutually exclusive + * with IMXML_NO_SUPPORT_DOUBLE_QUOTES, but technically both can be set if + * you can guarantee that there are no attribute values in the XML, which + * will improve performance and reduce code size + * IMXML_NO_SUPPORT_DOUBLE_QUOTES + * Same as IMXML_NO_SUPPORT_SINGLE_QUOTES, but for "double quotes" + * IMXML_NO_SUPPORT_NAMESPACES + * If set, the parser will not manage namespaces. This is by far the most + * performance-critical flag and it's heavily recommended to set it if at all + * possible. Even when this flag is set, you can check xml_attr for + * "xmlns", check xml_tag for prefixes (you can still use + * xml_tag_remove_namespace for convenience), and manage namespaces yourself, + * which is almost certain to be more efficient than this general solution. + * IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD + * If set, imxml_default_namespace_resolve does not resolve namespaces + * defined within the current tag. E.g. The namespace of `foo` in `` would NOT be "foo.com", but instead the default + * namespace of the enclosing tag (which may be empty ""). This lookahead is + * rarely necessary in practice and significantly impacts performance, so, + * even if you're using the library's default namespace handling (which + * itself is rarely optimal), it's strongly recommended to disable this if + * possible. + * IMXML_NO_SUPPORT_ENTITIES + * If set, xml_content ignores &entities;. This is technically safe to + * set, even if you can't guarantee that your XML does not contain entities. + * This means that entities will simply be reported as regular text, which is + * fine if you don't care about resolving the entities. + * IMXML_NO_SUPPORT_CUSTOM_ENTITIES + * If set, the parser ignores !ENTITY definitions in and will not + * resolve non-standard &entities;. Note that this flag will be ignored if + * IMXML_NO_SUPPORT_ENTITIES is set. + * IMXML_SIMD_DEFAULT and IMXML_SIMD_DENSE + * IMXML_SIMD_DEFAULT determines which instruction set is used for parsing + * when it's probable that a structurally relevant character WILL NOT be + * within a single vector window (e.g. when parsing content). + * IMXML_SIMD_DENSE determines which instruction set is used for parsing when + * it's probable that a structurally relevant character WILL be within a + * single vector window (e.g. when parsing tag arguments and values). + * Possible values are SCALAR, MMX, SSE2, and AVX2. Defaults to SSE2. When + * IMXML_SIMD_DEFAULT is SSE2 or lower, IMXML_SIMD_DENSE will default ot + * IMXML_SIMD_DEFAULT. If it's higher, IMXML_SIMD_DENSE will be set to SSE2. + * If you change these, be sure to benchmark, as it's not uncommon for SSE2 + * to outperform AVX2 in certain configurations for certain workloads. It's + * even possible for SCALAR to outperform everything else depending on the + * exact data, worload, configuration, compiler, and optimization flags. + * + * === OTHER FLAGS AND #DEFINES === + * IMXML_LIT_PAD + * This is just for convenience. You can use it to pad string literals in + * code so the SIMD operations don't read out-of-bounds. Usage example + * `ImxmlCharSequence xml = "" IMXML_LIT_PAD;` + * IMXML_CUSTOM_STDBOOL_H + * If set, the library will not include stdint.h, and you must provide + * `uint8_t`, `uint16_t`, `uint32_t`, and `SIZE_MAX`. + * IMXML_CUSTOM_STDINT_H + * If set, the library will not include stdbool.h, and you must provide + * `bool`, `true`, and `false`. + * IMXML_CUSTOM_SIMD_INTRINSICS + * If set, the library will not include emmintrin.h/immintrin.h, and you must + * provide all necessary SIMD intrinsics. This should only be necessary if + * your compiler doesn't support those headers, and/or if you're generating + * custom imxml_seek__next_char_XXX functions with different intrinsics. + * IMXML_SIMD_DEFAULT_INSTRUCTIONS and IMXML_SIMD_DENSE_INSTRUCTIONS + * These are the arguments passed to the imxml_seek_next_char_XXX functions. + * You should only set these if you need to support an instruction set that + * isn't supported by this library (i.e. MMX, SSE2, AVX2). + * IMXMLAPI + * Defaults to `static inline`. If you want to compile this library into an + * object file, you might want to change this. + * IMXML_CUSTOM_CHAR + * If set, you must define a `ImxmlChar` type. You almost certainly don't + * want to do that. `sizeof(ImxmlChar) != 1` is UB. + * IMXML_CUSTOM_CHAR_SEQUENCE + * If set, you must define a `ImxmlCharSequence` type. This is useful if you + * also have a custom string type (see IMXML_CUSTOM_STRING) that uses + * non-const data. + * IMXML_CUSTOM_STRING + * If set, you must define a `ImxmlString` type. You must also define + * IMXML_STRCOUNT and IMXML_STRITEMS as the identifier for your string's + * count/length and items/chars field respectively. Furthermore, you must + * implement/define the following functions/macros: imxml_strsubcstr, + * imxml_strlit, imxml_strsubcstr, imxml_streql. + * IMXML_CUSTOM_ENTITIES_MAX_STATIC_SIZE + * The maximum number of custom entities the default custom entity + * implementation can support. Note that if more entities are encountered, + * they will be silently ignored. Irrelevant if + * IMXML_NO_SUPPORT_CUSTOM_ENTITIES is set. Defaults to 255. + * IMXML_NAMESPACE_STACK_MAX_STATIC_SIZE + * The maximum namespace depth the default implementation can support. Note + * that if namespaces encountered after this maximum has been reached will be + * silently ignored. Irrelevant if IMXML_NO_SUPPORT_NAMESPACES is set. + * Defaults to 255. + * THREADLOCAL + * You can define THREADLOCAL to be nothing (e.g. `#define THREADLOCAL`) if + * you know you won't be using multithreading (this can improve performance + * and simplifies the emitted code), or if you have one parser that calls + * default entity/namespace implementation functions on multiple threads + * (though that's not recommended and you should probably consider just + * creating your own implementations at that point). + * + * === MULTITHREADING === + * This library, by default, has rudimentary thread-safety in the form of + * thread-local global variables. Thread-safety is only guaranteed when a + * single parser runs in a thread. If there are multiple parsers running + * concurrently in the same thread, or one parser runs in multiple threads, + * things will break. Note that, in practice, the only reasonable + * multi-threading approach with this library is parsing multiple different + * documents (with different parsers) on separate threads. Note that this + * library will max out a core, so it's generally best to not run more threads + * than the number of physical cores on the machine. + * + * === A NOTE ON THE DEFAULT CUSTOM ENTITY AND NAMESPACE IMPLEMENTATIONS === + * The imxml_default_XXX functions are included so you don't have to implement + * your own if you're just doing some quick and dirty parsing. But if you + * intend on using this library in a more sophisticated context, these default + * implementations should be replaced by custom code for both performance and + * correctness reasons. The two correctness-related issues with the default + * implementations are that they are limited to a static + * entity-size/namespace-depth, and that they are only thread-safe if each + * parser runs in its own thread. + * + * Creating a custom implementation for custom entities is relatively easy: You + * just need to call xml_parse_header with a custom register_entity callback + * and call xml_entity_resolve with a custom get_entity callback. + * + * Creating a custom implementation for namespace management is substantially + * more involved and not recommended unless you are parsing arbitrary and + * unboundable XML. Firstly, you need to set the namespace_push and + * namespace_pop callbacks in the XmlParser struct. Beside that, you also need + * to provide imxml_default_ns_pop_to_depth_. I'll note again that, in all + * likelihood, this is not what you want to be doing. You're almost certainly + * able to use the regular xml_tag, xml_attr, and xml_value APIs to accurately + * resolve namespaces in a more readable and performant way than would be + * possible with a generalized namespace management solution. + */ + +/* The numbers here aren't super important, we just care that smaller + * instruction sets are smaller so we can compare them. */ +#define SCALAR 100 +#define MMX 6400 /*TODO maybe MMX_NO_EMMS and MMX_FEMS ?*/ +#define SSE2 12800 +#define AVX2 25600 + +#ifndef IMXML_SIMD_DEFAULT +#define IMXML_SIMD_DEFAULT SSE2 +#endif +#ifndef IMXML_SIMD_DENSE +#if IMXML_SIMD_DEFAULT >= SSE2 +#define IMXML_SIMD_DENSE SSE2 +#else +#define IMXML_SIMD_DENSE IMXML_SIMD_DEFAULT +#endif /*IMXML_SIMD_DEFAULT >= SSE2*/ +#endif /*ifndef IMXML_SIMD_DENSE*/ + +#define IMXML_SCALAR (IMXML_SIMD_DEFAULT == SCALAR || IMXML_SIMD_DENSE == SCALAR) +#define IMXML_MMX (IMXML_SIMD_DEFAULT == MMX || IMXML_SIMD_DENSE == MMX) +#define IMXML_SSE2 (IMXML_SIMD_DEFAULT == SSE2 || IMXML_SIMD_DENSE == SSE2) +#define IMXML_AVX2 (IMXML_SIMD_DEFAULT == AVX2 || IMXML_SIMD_DENSE == AVX2) + +#include +#if !defined(IMXML_CUSTOM_STDBOOL_H) && defined(__STDC_VERSION__) +#include +#endif +#ifndef IMXML_CUSTOM_STDINT_H +#include +#endif +#if !defined(IMXML_CUSTOM_SIMD_INTRINSICS) && !IMXML_SCALAR +#if IMXML_AVX2 +#include +#else +#include +#endif /*AVX2*/ +#endif /*IMXML_CUSTOM_SIMD_INTRINSICS*/ + +#ifndef NODISCARD +#if defined(__clang__) || defined(__GNUC__) +#define NODISCARD __attribute__((warn_unused_result)) +#else +#define NODISCARD +#endif +#endif + +#ifndef ASSERT +#ifndef NDEBUG +#define ASSERT(X) if (UNLIKELY(!(X))) __asm__("int3") +#else +#define ASSERT(X) +#endif +#endif + +#ifndef ASSUME +#ifndef NDEBUG +#define ASSUME ASSERT +#else /*NDEBUG*/ +#if defined(__clang__) +#define ASSUME __builtin_assume +#elif defined(__GNUC__) +#define ASSUME(X) if (!(X)) __builtin_unreachable() +#else +#define ASSUME(X) +#endif +#endif /*NDEBUG*/ +#endif /*ASSUME*/ + +#ifndef ALIGN +#if defined(__clang__) || defined(__GNUC__) +#define ALIGN(X) __attribute__((aligned(X))) +#else +#define ALIGN(X) +#endif +#endif + +#ifndef UNLIKELY +#if defined(__clang__) || defined(__GNUC__) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define UNLIKELY(x) (x) +#endif +#endif +#ifndef LIKELY +#if defined(__clang__) || defined(__GNUC__) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define LIKELY(x) (x) +#endif +#endif + +#ifndef THREADLOCAL +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define THREADLOCAL _Thread_local +#elif defined(__clang__) || defined(__GNUC__) +#define THREADLOCAL __thread +#else +#error imxml: THREADLOCAL undefined. Use C11 or newer, clang, gcc, or define \ +THREADLOCAL yourself. You may define it as nothing (i.e. `#define \ +THREADLOCAL`), though that may cause issues when using default entity and \ +namespace implementations, and unicode entity decoding in a multithreaded \ +context. +#endif +#endif + +#ifndef __STDC_VERSION__ +/* C89 compatibility */ +#if defined(__clang__) || defined(__GNUC__) +#define inline __inline__ +#define restrict __restrict__ +#else +#define inline /*inline*/ +#define restrict /*restrict*/ +#endif + +#define bool uint8_t +#define true 1 +#define false 0 +#endif /*__STDC_VERSION__*/ + +#ifndef IMXML_NO_CHECK_BOUNDS +#define IMXML_CHECK_BOUNDS +#endif +#ifndef IMXML_NO_SUPPORT_COMMENTS +#define IMXML_SUPPORT_COMMENTS +#endif +#ifndef IMXML_NO_SUPPORT_CDATA +#define IMXML_SUPPORT_CDATA +#endif +#ifndef IMXML_NO_SUPPORT_SINGLE_QUOTES +#define IMXML_SUPPORT_SINGLE_QUOTES +#endif +#ifndef IMXML_NO_SUPPORT_DOUBLE_QUOTES +#define IMXML_SUPPORT_DOUBLE_QUOTES +#endif +#ifndef IMXML_NO_SUPPORT_NAMESPACES +#define IMXML_SUPPORT_NAMESPACES +#endif +#ifndef IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD +#define IMXML_SUPPORT_NAMESPACE_LOOKAHEAD +#endif +#ifndef IMXML_NO_SUPPORT_ENTITIES +#define IMXML_SUPPORT_ENTITIES +#ifndef IMXML_NO_SUPPORT_CUSTOM_ENTITIES +#define IMXML_SUPPORT_CUSTOM_ENTITIES +#endif +#endif /*IMXML_NO_SUPPORT_ENTITIES*/ + +#ifdef IMXML_CHECK_BOUNDS +#define IF_BOUNDS(x) x +#else +#define IF_BOUNDS(x) +#endif +#ifdef IMXML_SUPPORT_SINGLE_QUOTES +#define IF_SINGLE_QUOTES(x) x +#else +#define IF_SINGLE_QUOTES(x) +#endif +#ifdef IMXML_SUPPORT_DOUBLE_QUOTES +#define IF_DOUBLE_QUOTES(x) x +#else +#define IF_DOUBLE_QUOTES(x) +#endif +#ifdef IMXML_SUPPORT_COMMENTS +#define IF_COMMENTS(x) x +#else +#define IF_COMMENTS(x) +#endif +#ifdef IMXML_SUPPORT_CDATA +#define IF_CDATA(x) x +#else +#define IF_CDATA(x) +#endif +#if defined(IMXML_SUPPORT_CDATA) || defined(IMXML_SUPPORT_COMMENTS) +#define IF_COMMENTS_OR_CDATA(x) x +#else +#define IF_COMMENTS_OR_CDATA(x) +#endif + +#define scalar_set1(x) ((uint8_t)(x)) +#define scalar_loadu(p) (*(p)) +#define scalar_cmpeq(a, b) ((uint8_t)((a) == (b) ? 0xffu : 0u)) +#define scalar_or(a, b) ((uint8_t)((a) | (b))) +#define scalar_movemask(x) ((unsigned)((x) != 0)) +#define IMXML_SCALAR_INSTRUCTIONS 1, uint8_t, uint8_t, uint8_t, scalar_set1, scalar_loadu, scalar_cmpeq, scalar_or, scalar_movemask, {}, {} + +#define mmx_loadu(PTR) *(__m64 const*)PTR /*TODO this is actually an unaligned load, which may crash on some platforms*/ +#define IMXML_MMX_INSTRUCTIONS 8, uint8_t, __m64, void, _mm_set1_pi8, mmx_loadu, _mm_cmpeq_pi8, _mm_or_si64, _mm_movemask_pi8, {},_mm_empty(); + +#define IMXML_SSE2_INSTRUCTIONS 16, uint16_t, __m128i, __m128i_u, _mm_set1_epi8, _mm_loadu_si128, _mm_cmpeq_epi8, _mm_or_si128, _mm_movemask_epi8, {}, {} + +#define IMXML_AVX2_INSTRUCTIONS 32, uint32_t, __m256i, __m256i_u, _mm256_set1_epi8, _mm256_loadu_si256, _mm256_cmpeq_epi8, _mm256_or_si256, _mm256_movemask_epi8, {}, {} + +#ifndef IMXML_SIMD_DEFAULT_INSTRUCTIONS +#if IMXML_SIMD_DEFAULT == SCALAR +#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_SCALAR_INSTRUCTIONS +#elif IMXML_SIMD_DEFAULT == MMX +#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_MMX_INSTRUCTIONS +#elif IMXML_SIMD_DEFAULT == SSE2 +#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_SSE2_INSTRUCTIONS +#elif IMXML_SIMD_DEFAULT == AVX2 +#define IMXML_SIMD_DEFAULT_INSTRUCTIONS IMXML_AVX2_INSTRUCTIONS +#endif +#endif +#ifndef IMXML_SIMD_DENSE_INSTRUCTIONS +#if IMXML_SIMD_DENSE == SCALAR +#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_SCALAR_INSTRUCTIONS +#elif IMXML_SIMD_DENSE == MMX +#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_MMX_INSTRUCTIONS +#elif IMXML_SIMD_DENSE == SSE2 +#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_SSE2_INSTRUCTIONS +#elif IMXML_SIMD_DENSE == AVX2 +#define IMXML_SIMD_DENSE_INSTRUCTIONS IMXML_AVX2_INSTRUCTIONS +#endif +#endif + +#define IMXML_LIT_PAD "\0""123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" /* NUL + 63 bytes for AVX512 */ + +/* === API === */ + +#ifndef IMXMLAPI +/* NOTE: inline gains about 5% in certain cases, and does't ever seem to hurt, + * so I'm enabling it by default */ +#define IMXMLAPI static inline +#endif + +#ifndef IMXML_CUSTOM_CHAR +/* NOTE sizeof(ImxmlChar) must be 1 */ +typedef char ImxmlChar; +#endif +#ifndef IMXML_CUSTOM_CHAR_SEQUENCE +typedef ImxmlChar const* ImxmlCharSequence; +#endif + +#ifndef IMXML_CUSTOM_STRING +typedef size_t ImxmlStrLen; +typedef struct { + ImxmlStrLen count; + ImxmlChar const* items; +} ALIGN(16) ImxmlString; +#define IMXML_STRCOUNT count +#define IMXML_STRITEMS items +#else /*IMXML_CUSTOM_STRING*/ +#ifndef IMXML_STRCOUNT +#error Must define IMXML_STRCOUNT as the `count`/`size` field of your string struct +#endif /*IMXML_STRCOUNT*/ +#ifndef IMXML_STRITEMS +#error Must define IMXML_STRITEMS as the `items`/`chars` field of your string struct +#endif /*IMXML_STRITEMS*/ +#endif /*IMXML_CUSTOM_STRING*/ + +typedef struct { + ImxmlString prefix; + ImxmlString ns; +} ALIGN(32) ImxmlNamespace; + +typedef struct { + ImxmlString current_tag; + ImxmlCharSequence head; + #ifdef IMXML_SUPPORT_NAMESPACES + size_t depth; + void (*namespace_push)(size_t depth, ImxmlString prefix, ImxmlString ns); + void (*namespace_pop)(void); + #endif + bool in_tag; +} ALIGN(64) XmlParser; + +typedef enum { + XML_CONTENT_NONE = 0, + XML_CONTENT_TEXT, + XML_CONTENT_ENTITY, + XML_CONTENT_TAG, + XML_CONTENT_COMMENT +} XmlContentType; +typedef struct { + ImxmlString data; + XmlContentType type; +} ALIGN(32) XmlContent; +typedef struct { + ImxmlString key; + ImxmlString value; +} ALIGN(32) ImxmlEntityDefinition; + +IMXMLAPI bool imxml_memeql_ (ImxmlCharSequence a, ImxmlCharSequence b, size_t n); +#ifndef IMXML_CUSTOM_STRING +IMXMLAPI ImxmlString imxml_strsubcstr (ImxmlCharSequence cstr, ImxmlStrLen len); +#ifdef __STDC_VERSION__ +#define imxml_strlit(lit) (ImxmlString){.IMXML_STRCOUNT = sizeof(lit) - 1, .IMXML_STRITEMS = "" lit} +#endif /*__STDC_VERSION__*/ +#define imxml_strlit89(res, lit) do { \ + (res).IMXML_STRITEMS = lit; \ + (res).IMXML_STRCOUNT = sizeof(""lit)-1; \ +} while (0) +#endif /*IMXML_CUSTOM_STRING*/ + +IMXMLAPI ImxmlString xml_tag (XmlParser* p); +IMXMLAPI ImxmlString xml_attr (XmlParser* p); +/* NOTE This will just get the next quoted text, which means you should only + * use this if you KNOW there is a following attribute-value. If you're unsure, + * call xml_attribute first. */ +IMXMLAPI ImxmlString xml_value (XmlParser* p); + +IMXMLAPI bool xml_has_children (XmlParser* p); +IMXMLAPI ImxmlString xml_skip_element (XmlParser* p); + +typedef ImxmlString (*ImxmlEntityGetFunc)(ImxmlString key); +typedef void (*ImxmlEntityRegisterFunc)(ImxmlString key, ImxmlString value); +/* Returns the value for an &entity;. If it's a character entity (e.g. E), + * the returned value is a view into a global thread-local buffer that will be + * overwritten when the next character entity is decoded. It's the caller's + * responsibility to either immediately use the value (before another call to + * xml_entity_resolve is made on the same thread) or duplicate it. */ +IMXMLAPI ImxmlString xml_entity_resolve ( + ImxmlString entity_name, + ImxmlEntityGetFunc get_entity +); +/* Returns a string representing the content. See `xml_entity_resolve` + * regarding handling of character entity resolution. */ +IMXMLAPI ImxmlString xml_content_to_string ( + XmlContent content, + ImxmlEntityGetFunc get_entity +); + +IMXMLAPI XmlContent xml_content (XmlParser* p); + +IMXMLAPI bool xml_tag_expect_str (XmlParser* p, ImxmlString expected); +IMXMLAPI bool xml_attr_expect_str (XmlParser* p, ImxmlString expected); +#ifdef __STDC_VERSION__ +#define xml_tag_expect(p, s) xml_tag_expect_str((p), imxml_strlit(s)) +#define xml_attr_expect(p, s) xml_attr_expect_str((p), imxml_strlit(s)) +#endif /*__STDC_VERSION__*/ + +IMXMLAPI bool xml_parse_doctype ( + XmlParser* p, + ImxmlEntityRegisterFunc register_entity +); +IMXMLAPI bool xml_parse_header ( + XmlParser* p, + ImxmlEntityRegisterFunc register_entity +); + +#ifdef IMXML_SUPPORT_CUSTOM_ENTITIES +/* Call before parsing a document */ +IMXMLAPI void imxml_default_entity_reset (void); +IMXMLAPI void imxml_default_entity_register (ImxmlString key, ImxmlString value); +IMXMLAPI ImxmlString imxml_default_entity_get (ImxmlString key); +#endif /*IMXML_SUPPORT_CUSTOM_ENTITIES*/ + +/* NOTE All imxml_default_namespace_* functions are meant to work in tandem. + * I.e., require custom behavior, you'll need to reimplement all the callbacks + * and using any of the default implementations here is UB */ +#ifdef IMXML_SUPPORT_NAMESPACES +/* Call before parsing a document */ +IMXMLAPI void imxml_default_namespace_reset (void); +/* NOTE Only relevant when using the default namespace implementation */ +/* Returns the namespace for `id` where `id` is the full tag with prefix (the + * result of `xml_tag`). By default, this function performs a lookahead to + * check for namespaces that are being opened in the current tag. This incurs a + * significant performance cost and can be disabled via + * IMXML_NO_SUPPORT_NAMESPACE_LOOKAHEAD which, if set, will not consider any + * namespaces that are being opened in the current tag. E.g. `` will NOT be in the "foo.com" namespace. */ +IMXMLAPI ImxmlNamespace imxml_default_namespace_resolve (XmlParser* p, ImxmlString id); +#endif /*IMXML_SUPPORT_NAMESPACES*/ +IMXMLAPI ImxmlString xml_tag_remove_namespace (ImxmlString tag); + +#ifdef IMXML_LINUX +IMXMLAPI char* imxml_linux_file_open ( + char const* const file_name, + size_t* const mapped_size, + bool populate /* load entire file into memory instead of streaming from disk */ +); +IMXMLAPI void imxml_linux_file_close (char* file, size_t file_size); +#endif /*IMXML_LINUX*/ + +/* === END OF API === */ + +#ifdef IMXML_IMPLEMENTATION + +IMXMLAPI bool imxml_memeql_ ( + ImxmlCharSequence const a, + ImxmlCharSequence const b, + size_t const n +) { + size_t i; + for (i = 0; i < n; ++i) { + if (a[i] != b[i]) return false; + } + return true; +} + +#ifndef IMXML_CUSTOM_STRING +IMXMLAPI ImxmlString imxml_strsubcstr (ImxmlCharSequence const cstr, ImxmlStrLen len) { + ImxmlString res; + res.IMXML_STRCOUNT = len; + res.IMXML_STRITEMS = cstr; + return res; +} +IMXMLAPI bool imxml_streql (ImxmlString a, ImxmlString b) { + if (a.IMXML_STRCOUNT != b.IMXML_STRCOUNT) return false; + return imxml_memeql_(a.IMXML_STRITEMS, b.IMXML_STRITEMS, a.IMXML_STRCOUNT); +} +#endif /*IMXML_CUSTOM_STRING*/ + +IMXMLAPI bool is_whitespace (ImxmlChar c) { + return c == ' ' + || c == '\t' + || c == '\r' + || c == '\n' + ; +} + +#ifndef imxml_ctz +#if defined(__builtin_ctz) || (defined(__clang__) || defined(__GNUC__)) +#define imxml_ctz (uint8_t)__builtin_ctz +#else +static inline uint8_t imxml_ctz(uint64_t x) { + uint8_t n = 0; + while (!(x & 1)) { + x >>= 1; + n += 1; + } + return n; +} +#endif +#endif /*imxml_ctz*/ + +#define IMXML_FUNCTION_GENERATOR_xml_skip_single_delim( \ + SUFFIX, \ + STEP, \ + MASK_T, \ + VEC_T, \ + VEC_TU, \ + SET1, \ + LOADU, \ + CMPEQ, \ + VEC_OR, \ + MOVEMASK, \ + PREAMBLE, \ + POSTAMBLE \ +) \ +IMXMLAPI bool xml_skip_single_delim##SUFFIX (XmlParser* const p, ImxmlChar delimiter) { \ + VEC_T const qt = SET1(delimiter); \ + IF_BOUNDS( \ + VEC_T const zero = SET1(0); \ + ) \ + PREAMBLE \ + while (true) { \ + VEC_T block = LOADU((VEC_TU const*)(p->head)); \ + VEC_T match = CMPEQ(block, qt); \ + uint8_t offset; \ + MASK_T mask; \ + IF_BOUNDS( \ + VEC_T cmp_zero = CMPEQ(block, zero); \ + match = VEC_OR(cmp_zero, match); \ + ) \ + mask = (MASK_T)MOVEMASK(match); \ + if (!mask) { \ + p->head += STEP; \ + continue; \ + } \ + offset = imxml_ctz(mask); \ + p->head += offset; \ + IF_BOUNDS( \ + if (*p->head == 0) { \ + POSTAMBLE \ + return false; \ + } \ + ) \ + p->head += 1; \ + POSTAMBLE \ + return true; \ + } \ +} + +#define GEN_FUNC(suffix, x) IMXML_FUNCTION_GENERATOR_xml_skip_single_delim(suffix, x) + +GEN_FUNC(_, IMXML_SIMD_DENSE_INSTRUCTIONS) + +#undef GEN_FUNC + +#define xml_skip_sstring(p) xml_skip_single_delim_(p, '\'') +#define xml_skip_dstring(p) xml_skip_single_delim_(p, '"') + +#define IMXML_FUNCTION_GENERATOR_xml_skip_double_delim_gt( \ + SUFFIX, \ + STEP, \ + MASK_T, \ + VEC_T, \ + VEC_TU, \ + SET1, \ + LOADU, \ + CMPEQ, \ + VEC_OR, \ + MOVEMASK, \ + PREAMBLE, \ + POSTAMBLE \ +) \ +IMXMLAPI bool xml_skip_double_delim_gt##SUFFIX (XmlParser* const p, ImxmlCharSequence delim) { \ + VEC_T const gt = SET1('>'); \ + IF_BOUNDS( \ + VEC_T const zero = SET1(0); \ + ) \ + PREAMBLE \ + while (true) { \ + VEC_T block = LOADU((VEC_TU const*)(p->head)); \ + VEC_T match = CMPEQ(block, gt); \ + MASK_T mask; \ + uint8_t offset; \ + IF_BOUNDS( \ + VEC_T cmp_zero = CMPEQ(block, zero); \ + match = VEC_OR(cmp_zero, match); \ + ) \ + mask = (MASK_T)MOVEMASK(match); \ + if (mask) { \ + offset = imxml_ctz(mask); \ + p->head += offset; \ + IF_BOUNDS( \ + if (*p->head == 0) { \ + POSTAMBLE \ + return false; \ + } \ + ) \ + p->head += 1; \ + /* NOTE the following can't underflow because this only gets called + * in contexts where we have already parsed >4 bytes ("