leptris 1.9.210.0-arm-linux → 1.9.213.0-arm-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.adoc +49 -11
  4. data/Rakefile +1 -1
  5. data/TODO.restructure/17-perf-drift-record.md +15 -0
  6. data/lib/leptris/version.rb +1 -1
  7. data/lib/leptris/xml/descriptor.rb +59 -2
  8. data/lib/leptris/xml/plan_value.rb +53 -2
  9. data/lib/libleptris.so +0 -0
  10. data/vendor-src/README.md +1 -1
  11. data/vendor-src/libleptris/CHANGELOG.md +57 -0
  12. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  13. data/vendor-src/libleptris/TODO.max-perf/00-INDEX.md +4 -0
  14. data/vendor-src/libleptris/TODO.max-perf/interleaved-parse.md +67 -0
  15. data/vendor-src/libleptris/benchmarks/CMakeLists.txt +13 -0
  16. data/vendor-src/libleptris/benchmarks/parse/bench_parse_ab.cpp +83 -0
  17. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +658 -0
  18. data/vendor-src/libleptris/src/leptris/leptris_internal.h +1 -0
  19. data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +368 -1
  20. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +466 -24
  21. data/vendor-src/libleptris/src/leptris/xpath/parser.c +69 -7
  22. data/vendor-src/libleptris/src/leptris/xpath/vm.c +3 -1
  23. data/vendor-src/libleptris/src/leptris/xpath/xpath_internal.h +8 -1
  24. data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +5 -2
  25. data/vendor-src/libleptris/src/leptris/xslt/xslt_internal.h +9 -0
  26. data/vendor-src/libleptris/test/CMakeLists.txt +1 -0
  27. data/vendor-src/libleptris/test/parser/test_interleaved.cpp +223 -0
  28. data/vendor-src/libleptris/test/xpath/test_xpath.cpp +4 -1
  29. data/vendor-src/libleptris/test/xquery/qt3/README.md +12 -0
  30. data/vendor-src/libleptris/test/xquery/qt3/fn/day-from-date.xml +249 -0
  31. data/vendor-src/libleptris/test/xquery/qt3/fn/day-from-dateTime.xml +249 -0
  32. data/vendor-src/libleptris/test/xquery/qt3/fn/hours-from-dateTime.xml +249 -0
  33. data/vendor-src/libleptris/test/xquery/qt3/fn/hours-from-time.xml +249 -0
  34. data/vendor-src/libleptris/test/xquery/qt3/fn/implicit-timezone.xml +249 -0
  35. data/vendor-src/libleptris/test/xquery/qt3/fn/minutes-from-dateTime.xml +250 -0
  36. data/vendor-src/libleptris/test/xquery/qt3/fn/minutes-from-time.xml +249 -0
  37. data/vendor-src/libleptris/test/xquery/qt3/fn/month-from-date.xml +249 -0
  38. data/vendor-src/libleptris/test/xquery/qt3/fn/month-from-dateTime.xml +249 -0
  39. data/vendor-src/libleptris/test/xquery/qt3/fn/parse-ietf-date.xml +1268 -0
  40. data/vendor-src/libleptris/test/xquery/qt3/fn/seconds-from-dateTime.xml +250 -0
  41. data/vendor-src/libleptris/test/xquery/qt3/fn/seconds-from-time.xml +249 -0
  42. data/vendor-src/libleptris/test/xquery/qt3/fn/timezone-from-date.xml +333 -0
  43. data/vendor-src/libleptris/test/xquery/qt3/fn/timezone-from-dateTime.xml +250 -0
  44. data/vendor-src/libleptris/test/xquery/qt3/fn/timezone-from-time.xml +249 -0
  45. data/vendor-src/libleptris/test/xquery/qt3/fn/year-from-date.xml +253 -0
  46. data/vendor-src/libleptris/test/xquery/qt3/fn/year-from-dateTime.xml +249 -0
  47. data/vendor-src/libleptris/test/xquery/qt3/op/add-dayTimeDuration-to-date.xml +467 -0
  48. data/vendor-src/libleptris/test/xquery/qt3/op/add-dayTimeDuration-to-dateTime.xml +436 -0
  49. data/vendor-src/libleptris/test/xquery/qt3/op/add-dayTimeDuration-to-time.xml +432 -0
  50. data/vendor-src/libleptris/test/xquery/qt3/op/add-dayTimeDurations.xml +850 -0
  51. data/vendor-src/libleptris/test/xquery/qt3/op/add-yearMonthDuration-to-date.xml +441 -0
  52. data/vendor-src/libleptris/test/xquery/qt3/op/add-yearMonthDuration-to-dateTime.xml +305 -0
  53. data/vendor-src/libleptris/test/xquery/qt3/op/add-yearMonthDurations.xml +440 -0
  54. data/vendor-src/libleptris/test/xquery/qt3/op/dayTimeDuration-greater-than.xml +859 -0
  55. data/vendor-src/libleptris/test/xquery/qt3/op/dayTimeDuration-less-than.xml +685 -0
  56. data/vendor-src/libleptris/test/xquery/qt3/op/divide-dayTimeDuration-by-dayTimeDuration.xml +410 -0
  57. data/vendor-src/libleptris/test/xquery/qt3/op/divide-dayTimeDuration.xml +807 -0
  58. data/vendor-src/libleptris/test/xquery/qt3/op/divide-yearMonthDuration-by-yearMonthDuration.xml +377 -0
  59. data/vendor-src/libleptris/test/xquery/qt3/op/divide-yearMonthDuration.xml +499 -0
  60. data/vendor-src/libleptris/test/xquery/qt3/op/duration-equal.xml +1477 -0
  61. data/vendor-src/libleptris/test/xquery/qt3/op/multiply-dayTimeDuration.xml +697 -0
  62. data/vendor-src/libleptris/test/xquery/qt3/op/multiply-yearMonthDuration.xml +685 -0
  63. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-dayTimeDuration-from-date.xml +377 -0
  64. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-dayTimeDuration-from-dateTime.xml +376 -0
  65. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-dayTimeDuration-from-time.xml +441 -0
  66. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-dayTimeDurations.xml +971 -0
  67. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-yearMonthDuration-from-date.xml +426 -0
  68. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-yearMonthDuration-from-dateTime.xml +377 -0
  69. data/vendor-src/libleptris/test/xquery/qt3/op/subtract-yearMonthDurations.xml +531 -0
  70. data/vendor-src/libleptris/test/xquery/qt3/op/yearMonthDuration-greater-than.xml +542 -0
  71. data/vendor-src/libleptris/test/xquery/qt3/op/yearMonthDuration-less-than.xml +542 -0
  72. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +128 -3
  73. data/vendor-src/libleptris/vcpkg.json +1 -1
  74. metadata +47 -2
@@ -1957,6 +1957,652 @@ fail:
1957
1957
  return NULL;
1958
1958
  }
1959
1959
 
1960
+ /* ============================================================================
1961
+ * Interleaved lane — TODO.max-perf/interleaved-parse, slice 1 (#1222).
1962
+ *
1963
+ * Second parse lane behind LEPTRIS_INTERLEAVED=1 (default OFF): the
1964
+ * scan phase ONLY appends to flat streams (element/text records + a
1965
+ * flat attr array, pure (off,len) views — no struct stores, no inline
1966
+ * NUL-termination, no restore pass), and a cache-sequential post-pass
1967
+ * replays the classic lane's exact field stores and wiring
1968
+ * (dp_wire_child, dp_split_hash_name, the attr tail, the doc shell).
1969
+ * Children are wired in the post-pass's doc-order replay — the
1970
+ * stream keeps only each depth's last-child record to chain siblings.
1971
+ *
1972
+ * Bail-to-classic (never wrong; one extra parse on bailing docs):
1973
+ * '<!' / '<?' / '&' anywhere, any xmlns attr, KEEP_ENTITY_REFS or
1974
+ * DTDATTR flags, len >= 2GiB, malformed input, depth over limit, OOM.
1975
+ * ============================================================================
1976
+ */
1977
+
1978
+ #define IL_NONE 0xFFFFFFFFu
1979
+
1980
+ typedef struct IlRec {
1981
+ uint32_t off; /* elem: raw name incl. prefix; text: content */
1982
+ uint32_t len;
1983
+ uint32_t parent; /* record index, IL_NONE for the root */
1984
+ uint32_t next_sib; /* record index, IL_NONE = last sibling */
1985
+ uint32_t line; /* byte offset of '<' (or text start) + 1 */
1986
+ uint32_t start_tag_end; /* byte offset after '>' (0 when !line_ok) */
1987
+ uint32_t elem_end; /* after close '>' (self-close = start) */
1988
+ uint32_t first_attr; /* attr array index, IL_NONE = none */
1989
+ uint32_t attr_count;
1990
+ uint8_t kind; /* 0 = element, 1 = text */
1991
+ uint8_t self_closing;
1992
+ uint8_t open_prefixed;
1993
+ uint8_t has_open_parent;
1994
+ } IlRec;
1995
+
1996
+ typedef struct IlAttr {
1997
+ uint32_t name_off, name_len, val_off, val_len;
1998
+ uint32_t has_ws; /* literal \t/\n/\r — §3.3.3 normalization */
1999
+ } IlAttr;
2000
+
2001
+ typedef struct IlCtx {
2002
+ IlRec* recs; size_t nrec, crec;
2003
+ IlAttr* attrs; size_t nattr, cattr;
2004
+ uint32_t open[DP_MAX_DEPTH];
2005
+ uint32_t last_child[DP_MAX_DEPTH];
2006
+ int depth;
2007
+
2008
+ } IlCtx;
2009
+
2010
+ static int il_push(IlCtx* c, const IlRec* r) {
2011
+ if (c->nrec == c->crec) {
2012
+ size_t nc = c->crec ? c->crec * 2 : 256;
2013
+ IlRec* g = (IlRec*)realloc(c->recs, nc * sizeof(IlRec));
2014
+ if (!g) return 0;
2015
+ c->recs = g; c->crec = nc;
2016
+ }
2017
+ c->recs[c->nrec++] = *r;
2018
+ return 1;
2019
+ }
2020
+
2021
+ static int il_push_attr(IlCtx* c, const IlAttr* a) {
2022
+ if (c->nattr == c->cattr) {
2023
+ size_t nc = c->cattr ? c->cattr * 2 : 128;
2024
+ IlAttr* g = (IlAttr*)realloc(c->attrs, nc * sizeof(IlAttr));
2025
+ if (!g) return 0;
2026
+ c->attrs = g; c->cattr = nc;
2027
+ }
2028
+ c->attrs[c->nattr++] = *a;
2029
+ return 1;
2030
+ }
2031
+
2032
+ static int il_ws_only(const char* p, size_t n) {
2033
+ for (size_t q = 0; q < n; q++)
2034
+ if (!IS_WS(p[q])) return 0;
2035
+ return 1;
2036
+ }
2037
+
2038
+ /* Stream phase: 1 = parsed (root_out/trail set), 0 = bail. */
2039
+ /* Slice 2: il_scan reads the SCRATCH copy (65-byte NUL runway
2040
+ * past len), so scans stop on the sentinel instead of paying a
2041
+ * bounds test per byte — classic's endgame discipline. Inline
2042
+ * probes of up to 48 bytes are safe: start <= len means
2043
+ * start+48 <= len+48 < len+65. */
2044
+ static LEPTRIS_ALWAYS_INLINE size_t il_name_end(const char* s,
2045
+ size_t j) {
2046
+ const char* p = s + j;
2047
+ for (;;) {
2048
+ char c = p[0];
2049
+ if (DP_UNLIKELY(!IS_NAME_CHAR(c))) break;
2050
+ c = p[1];
2051
+ if (DP_UNLIKELY(!IS_NAME_CHAR(c))) { p += 1; break; }
2052
+ c = p[2];
2053
+ if (DP_UNLIKELY(!IS_NAME_CHAR(c))) { p += 2; break; }
2054
+ c = p[3];
2055
+ if (DP_UNLIKELY(!IS_NAME_CHAR(c))) { p += 3; break; }
2056
+ p += 4;
2057
+ }
2058
+ return (size_t)(p - s);
2059
+ }
2060
+
2061
+ static int il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2062
+ uint32_t* root_out, size_t* trail_off,
2063
+ size_t* trail_len) {
2064
+ const char* send = s + len;
2065
+ size_t i = 0;
2066
+ const int line_ok = len < 0x7FFFFFFFu;
2067
+ *root_out = IL_NONE;
2068
+ *trail_off = *trail_len = 0;
2069
+
2070
+ if (len >= 3 && (unsigned char)s[0] == 0xEF &&
2071
+ (unsigned char)s[1] == 0xBB && (unsigned char)s[2] == 0xBF)
2072
+ i = 3;
2073
+ while (IS_WS(s[i])) i++; /* prolog ws — dropped (NUL stops) */
2074
+ if (i >= len) return 0;
2075
+
2076
+ for (;;) {
2077
+ /* ----- open tag at s[i] == '<' ----- */
2078
+ if (i + 1 >= len) return 0;
2079
+ char k = s[i + 1];
2080
+ if (k == '!' || k == '?') return 0;
2081
+ size_t j = i + 1;
2082
+ if (!IS_NAME_START(s[j])) return 0;
2083
+ size_t n0 = j;
2084
+ j = il_name_end(s, j);
2085
+ size_t nl = j - n0;
2086
+
2087
+ IlRec r;
2088
+ r.kind = 0;
2089
+ r.self_closing = 0;
2090
+ r.open_prefixed = 0;
2091
+ r.elem_end = 0;
2092
+ r.off = (uint32_t)n0;
2093
+ r.len = (uint32_t)nl;
2094
+ r.line = line_ok ? (uint32_t)i + 1u : 0u;
2095
+ r.parent = c->depth ? c->open[c->depth - 1] : IL_NONE;
2096
+ r.has_open_parent = c->depth ? 1u : 0u;
2097
+ r.next_sib = IL_NONE;
2098
+ r.first_attr = IL_NONE;
2099
+ if (nl >= 3)
2100
+ for (size_t q = n0; q < n0 + nl; q++)
2101
+ if (s[q] == ':') { r.open_prefixed = 1; break; }
2102
+
2103
+ uint32_t acount = 0;
2104
+ for (;;) {
2105
+ while (IS_WS(s[j])) j++;
2106
+ if (s[j] == '>') { j++; break; }
2107
+ if (s[j] == '/' && s[j + 1] == '>') {
2108
+ j += 2; r.self_closing = 1; break;
2109
+ }
2110
+ if (!IS_NAME_START(s[j])) return 0;
2111
+ size_t a0 = j;
2112
+ j = il_name_end(s, j);
2113
+ size_t al = j - a0;
2114
+ if (al >= 5 && s[a0] == 'x' && s[a0 + 1] == 'm' &&
2115
+ s[a0 + 2] == 'l' && s[a0 + 3] == 'n' &&
2116
+ s[a0 + 4] == 's')
2117
+ return 0; /* xmlns — classic owns ns machinery */
2118
+ while (IS_WS(s[j])) j++;
2119
+ if (s[j] != '=') return 0;
2120
+ j++;
2121
+ while (IS_WS(s[j])) j++;
2122
+ if (s[j] != '"' && s[j] != '\'') return 0;
2123
+ char qc = s[j++];
2124
+ size_t v0 = j;
2125
+ /* FUSED value scan (classic TODO 184 shape): 48 bytes
2126
+ * inline catching quote/&/ws in one pass, then memchr
2127
+ * for the quote + SIMD '&' check over the tail. The
2128
+ * scratch runway makes the 48B probe unconditionally
2129
+ * safe. '&' = bail (classic owns entities). */
2130
+ uint32_t aws = 0;
2131
+ const char* q = s + v0;
2132
+ const char* vend = NULL;
2133
+ while (q < s + v0 + 48) {
2134
+ char v = *q;
2135
+ if (v == qc) { vend = q; goto vdone; }
2136
+ if (v == '&') return 0;
2137
+ if (v == '\t' || v == '\n' || v == '\r') aws = 1;
2138
+ q++;
2139
+ }
2140
+ if (q >= send) return 0; /* ran to the sentinel */
2141
+ vend = (const char*)memchr(q, qc, (size_t)(send - q));
2142
+ if (!vend) return 0;
2143
+ if (q < vend &&
2144
+ leptris_text_contains(q, (size_t)(vend - q), '&'))
2145
+ return 0;
2146
+ for (const char* w = q; w < vend; w++) {
2147
+ if (*w == '\t' || *w == '\n' || *w == '\r') {
2148
+ aws = 1;
2149
+ break;
2150
+ }
2151
+ }
2152
+ vdone:
2153
+ ;
2154
+ size_t vl = (size_t)(vend - (s + v0));
2155
+ j = (size_t)(vend - s) + 1;
2156
+ IlAttr a = { (uint32_t)a0, (uint32_t)al,
2157
+ (uint32_t)v0, (uint32_t)vl, aws };
2158
+ if (!il_push_attr(c, &a)) return 0;
2159
+ if (acount == 0) r.first_attr = (uint32_t)(c->nattr - 1);
2160
+ acount++;
2161
+ }
2162
+ r.attr_count = acount;
2163
+ r.start_tag_end = line_ok ? (uint32_t)j : 0u;
2164
+ if (r.self_closing) r.elem_end = r.start_tag_end;
2165
+ if (!il_push(c, &r)) return 0;
2166
+ uint32_t ri = (uint32_t)(c->nrec - 1);
2167
+ /* names stay as NUL'd views into scratch (post-pass) */
2168
+ if (r.has_open_parent && c->last_child[c->depth - 1] != IL_NONE)
2169
+ c->recs[c->last_child[c->depth - 1]].next_sib = ri;
2170
+ c->last_child[c->depth - 1] = ri;
2171
+
2172
+ if (!r.self_closing) {
2173
+ extern LEPTRIS_THREAD_LOCAL int g_leptris_max_depth;
2174
+ int max_depth = g_leptris_max_depth > 0
2175
+ ? g_leptris_max_depth : DP_MAX_DEPTH;
2176
+ if (c->depth >= max_depth) return 0;
2177
+ c->open[c->depth] = ri;
2178
+ c->last_child[c->depth] = IL_NONE;
2179
+ c->depth++;
2180
+ } else if (c->depth == 0) {
2181
+ *root_out = ri;
2182
+ size_t t0 = j;
2183
+ while (IS_WS(s[j])) j++;
2184
+ if (s[j] != '\0') return 0;
2185
+ *trail_off = t0; *trail_len = j - t0;
2186
+ return 1;
2187
+ }
2188
+
2189
+ /* ----- content until the matching close ----- */
2190
+ for (;;) {
2191
+ size_t t0 = j;
2192
+ /* FUSED text scan: 48 bytes inline for '<'/'&' (the
2193
+ * common text run is short), then memchr('<') + SIMD
2194
+ * '&' over the tail. '&' = bail (classic owns entities). */
2195
+ const char* tq = s + t0;
2196
+ const char* tend = NULL;
2197
+ while (tq < s + t0 + 48) {
2198
+ char v = *tq;
2199
+ if (v == '<') { tend = tq; goto tdone; }
2200
+ if (v == '&') return 0;
2201
+ tq++;
2202
+ }
2203
+ if (tq >= send) return 0; /* ran to the sentinel */
2204
+ tend = (const char*)memchr(tq, '<', (size_t)(send - tq));
2205
+ if (!tend) return 0;
2206
+ if (tq < tend &&
2207
+ leptris_text_contains(tq, (size_t)(tend - tq), '&'))
2208
+ return 0;
2209
+ tdone:
2210
+ ;
2211
+ size_t tl = (size_t)(tend - (s + t0));
2212
+ j = (size_t)(tend - s);
2213
+ if (tl > 0 && !(drop_ws && il_ws_only(s + t0, tl))) {
2214
+ IlRec tr;
2215
+ tr.kind = 1;
2216
+ tr.self_closing = 0;
2217
+ tr.open_prefixed = 0;
2218
+ tr.start_tag_end = 0;
2219
+ tr.elem_end = 0;
2220
+ tr.first_attr = IL_NONE;
2221
+ tr.attr_count = 0;
2222
+ tr.off = (uint32_t)t0;
2223
+ tr.len = (uint32_t)tl;
2224
+ tr.parent = c->open[c->depth - 1];
2225
+ tr.has_open_parent = 1;
2226
+ tr.next_sib = IL_NONE;
2227
+ tr.line = line_ok ? (uint32_t)t0 + 1u : 0u;
2228
+ if (!il_push(c, &tr)) return 0;
2229
+ uint32_t ti = (uint32_t)(c->nrec - 1);
2230
+ if (c->last_child[c->depth - 1] != IL_NONE)
2231
+ c->recs[c->last_child[c->depth - 1]].next_sib = ti;
2232
+ c->last_child[c->depth - 1] = ti;
2233
+ }
2234
+ if (j + 1 >= len) return 0;
2235
+ char ck = s[j + 1];
2236
+ if (ck == '!' || ck == '?') return 0;
2237
+ if (ck != '/') {
2238
+ i = j; /* nested open tag */
2239
+ break; /* -> outer loop */
2240
+ }
2241
+ size_t cs = j + 2;
2242
+ if (!IS_NAME_START(s[cs])) return 0;
2243
+ size_t c0 = cs;
2244
+ cs = il_name_end(s, cs);
2245
+ size_t ccl = cs - c0;
2246
+ while (IS_WS(s[cs])) cs++;
2247
+ if (s[cs] != '>') return 0;
2248
+ cs++;
2249
+ if (c->depth == 0) return 0;
2250
+ uint32_t or_ = c->open[c->depth - 1];
2251
+ IlRec* o = &c->recs[or_];
2252
+ const char* cl = s + c0;
2253
+ size_t cll = ccl;
2254
+ if (o->open_prefixed) {
2255
+ const char* colon =
2256
+ (const char*)memchr(s + c0, ':', ccl);
2257
+ if (colon) {
2258
+ cll = ccl - (size_t)(colon + 1 - (s + c0));
2259
+ cl = colon + 1;
2260
+ }
2261
+ }
2262
+ if ((size_t)o->len != cll ||
2263
+ memcmp(s + o->off, cl, cll) != 0)
2264
+ return 0;
2265
+ o->elem_end = line_ok ? (uint32_t)cs : 0u;
2266
+ c->depth--;
2267
+ if (c->depth == 0) {
2268
+ *root_out = or_;
2269
+ size_t t0b = cs;
2270
+ while (IS_WS(s[cs])) cs++;
2271
+ if (s[cs] != '\0') return 0;
2272
+ *trail_off = t0b; *trail_len = cs - t0b;
2273
+ return 1;
2274
+ }
2275
+ j = cs; /* continue content at this depth */
2276
+ }
2277
+ }
2278
+ }
2279
+
2280
+
2281
+ /* Post-pass: build the public tree from the streams, replaying the
2282
+ * classic lane's exact stores. Returns NULL on OOM (caller frees). */
2283
+ static struct leptris_document* dp_il_build(
2284
+ char* scratch, size_t len, IlCtx* c, uint32_t root_rec,
2285
+ size_t trail_off, size_t trail_len, int drop_ws) {
2286
+ extern LeptrisArena* leptris_arena_create(size_t);
2287
+ extern LeptrisMemoryPool* leptris_pool_create_arena_backed(
2288
+ LeptrisArena*, int);
2289
+ extern void leptris_arena_destroy(LeptrisArena*);
2290
+
2291
+ /* scratch (with its NUL runway) is caller-built and becomes the
2292
+ * document's parse_scratch; pristine is the immutable copy. */
2293
+ char* pristine = (char*)malloc(len + 1);
2294
+ if (!pristine) goto oom_early;
2295
+ memcpy(pristine, scratch, len);
2296
+ pristine[len] = '\0';
2297
+
2298
+ {
2299
+ size_t need = c->nrec * sizeof(struct leptris_element) +
2300
+ c->nrec * sizeof(LeptrisTextNode) +
2301
+ c->nattr * sizeof(struct leptris_attribute) +
2302
+ 8 * 1024 + 64 * 1024;
2303
+ LeptrisArena* arena = leptris_arena_create(need);
2304
+ if (!arena) goto oom_early;
2305
+ LeptrisMemoryPool* pool = leptris_pool_create_arena_backed(arena, 1);
2306
+ if (!pool) { leptris_arena_destroy(arena); goto oom_early; }
2307
+
2308
+ struct leptris_document* doc =
2309
+ (struct leptris_document*)leptris_pool_alloc(
2310
+ pool, sizeof(struct leptris_document));
2311
+ if (!doc) goto oom_pool;
2312
+ memset(doc, 0, sizeof(*doc));
2313
+ doc->doc_pool_allocated = 1;
2314
+ doc->strict_mode = g_leptris_strict_mode;
2315
+ doc->pool = pool;
2316
+ doc->page_base = leptris_pool_get_base(pool);
2317
+ doc->ref_count = 1;
2318
+ doc->xml_buffer = pristine;
2319
+ doc->xml_buffer_len = len;
2320
+ doc->xml_buffer_needs_free = 1;
2321
+ doc->xml_buffer_slack = 0u;
2322
+ doc->parse_scratch = scratch;
2323
+
2324
+ /* line-breaks table (classic parity) */
2325
+ if (len < 0x7FFFFFFFu) {
2326
+ size_t cap = 256, n_ = 0;
2327
+ uint32_t* brks = (uint32_t*)malloc(cap * sizeof(uint32_t));
2328
+ if (!brks) goto oom_pool;
2329
+ for (size_t q = 0; q < len; q++) {
2330
+ if (scratch[q] != '\n') continue;
2331
+ if (n_ == cap) {
2332
+ cap *= 2;
2333
+ uint32_t* g = (uint32_t*)realloc(
2334
+ brks, cap * sizeof(uint32_t));
2335
+ if (!g) { free(brks); goto oom_pool; }
2336
+ brks = g;
2337
+ }
2338
+ brks[n_++] = (uint32_t)q;
2339
+ }
2340
+ doc->line_breaks = brks;
2341
+ doc->line_break_count = n_;
2342
+ }
2343
+
2344
+ /* Bulk struct blocks, exact counts. */
2345
+ struct leptris_element* eblk = NULL;
2346
+ LeptrisTextNode* tblk = NULL;
2347
+ struct leptris_attribute* ablk = NULL;
2348
+ size_t nelem = 0, ntext = 0;
2349
+ if (c->nrec) {
2350
+ eblk = (struct leptris_element*)leptris_pool_alloc(
2351
+ pool, c->nrec * sizeof(struct leptris_element));
2352
+ if (!eblk) goto oom_pool;
2353
+ memset(eblk, 0, c->nrec * sizeof(struct leptris_element));
2354
+ tblk = (LeptrisTextNode*)leptris_pool_alloc(
2355
+ pool, c->nrec * sizeof(LeptrisTextNode));
2356
+ if (!tblk) goto oom_pool;
2357
+ memset(tblk, 0, c->nrec * sizeof(LeptrisTextNode));
2358
+ }
2359
+ if (c->nattr) {
2360
+ ablk = (struct leptris_attribute*)leptris_pool_alloc(
2361
+ pool, c->nattr * sizeof(struct leptris_attribute));
2362
+ if (!ablk) goto oom_pool;
2363
+ memset(ablk, 0, c->nattr * sizeof(struct leptris_attribute));
2364
+ }
2365
+ /* Pool string arena for element names (writable — split_hash
2366
+ * NULs the colon there, as classic does on its copies). */
2367
+
2368
+ DParser pp;
2369
+ memset(&pp, 0, sizeof(pp));
2370
+ pp.pool = pool;
2371
+ pp.doc = doc;
2372
+
2373
+ /* struct map + per-record last-child STRUCT (for dp_wire_child's
2374
+ * last_child_stack) and depths. */
2375
+ LeptrisNode** smap = (LeptrisNode**)calloc(
2376
+ c->nrec ? c->nrec : 1, sizeof(LeptrisNode*));
2377
+ uint32_t* rdepth = (uint32_t*)calloc(
2378
+ c->nrec ? c->nrec : 1, sizeof(uint32_t));
2379
+ LeptrisNode** lc = (LeptrisNode**)calloc(
2380
+ c->nrec ? c->nrec : 1, sizeof(LeptrisNode*));
2381
+ if (!smap || !rdepth || !lc) {
2382
+ free(smap); free(rdepth); free(lc); goto oom_pool;
2383
+ }
2384
+
2385
+ for (size_t i = 0; i < c->nrec; i++) {
2386
+ IlRec* r = &c->recs[i];
2387
+ rdepth[i] = (r->parent == IL_NONE)
2388
+ ? 0 : rdepth[r->parent] + 1;
2389
+ if (r->kind == 0) {
2390
+ LeptrisElement e = &eblk[nelem++];
2391
+ e->base.type = LEPTRIS_NODE_TYPE_ELEMENT;
2392
+ e->base.line = r->line;
2393
+ e->base.frozen = 1;
2394
+ e->start_tag_end_off = r->start_tag_end;
2395
+ e->element_end_off = r->elem_end;
2396
+ /* Element name as a NUL'd scratch view — the byte
2397
+ * after the name (ws/'>'/'/') is dead after the scan,
2398
+ * same as attr names/values. Removes the per-element
2399
+ * name memcpy + the packed region. */
2400
+ scratch[r->off + r->len] = '\0';
2401
+ dp_split_hash_name(&pp, e, scratch + r->off, r->len);
2402
+
2403
+ /* attrs: dup first-wins + wiring (classic tail).
2404
+ * attr_count counts WIRED attrs only — the dup path
2405
+ * returns before the classic counter increment. */
2406
+ if (r->first_attr != IL_NONE) {
2407
+ uint32_t wired = 0;
2408
+ struct leptris_attribute* tail = NULL;
2409
+ for (uint32_t ai = 0; ai < r->attr_count; ai++) {
2410
+ IlAttr* a = &c->attrs[r->first_attr + ai];
2411
+ struct leptris_attribute* at =
2412
+ &ablk[r->first_attr + ai];
2413
+ /* NUL the name's trailing '=' in scratch —
2414
+ * accessors like attr_cname read it as a C
2415
+ * string; views alone aren't enough (RNG/CLI
2416
+ * diag text shipped "version='1"). The byte is
2417
+ * dead after the stream scan, so the write is
2418
+ * free. */
2419
+ scratch[a->name_off + a->name_len] = '\0';
2420
+ at->name_view = leptris_sv_from_ptr(
2421
+ scratch + a->name_off, a->name_len);
2422
+ if (a->has_ws) {
2423
+ char* norm = (char*)leptris_pool_alloc(
2424
+ pool, a->val_len + 1);
2425
+ if (!norm) { free(smap); free(rdepth);
2426
+ free(lc); goto oom_pool; }
2427
+ for (uint32_t w = 0; w < a->val_len; w++) {
2428
+ char ch = scratch[a->val_off + w];
2429
+ norm[w] = (ch == '\t' || ch == '\n' ||
2430
+ ch == '\r') ? ' ' : ch;
2431
+ }
2432
+ norm[a->val_len] = '\0';
2433
+ leptris_attr_value_set_heap(
2434
+ at, leptris_sv_from_cstr(norm));
2435
+ } else {
2436
+ scratch[a->val_off + a->val_len] = '\0';
2437
+ leptris_attr_value_set_heap(
2438
+ at, leptris_sv_from_ptr(
2439
+ scratch + a->val_off, a->val_len));
2440
+ }
2441
+ at->name_hash = 0; /* lazy-hash sentinel */
2442
+ at->ns_cache_off = 0;
2443
+ at->next_cp = 0;
2444
+ /* dup detection — first wins (#1200) */
2445
+ int dup = 0;
2446
+ for (struct leptris_attribute* d =
2447
+ leptris_element_get_first_attribute(e);
2448
+ d; d = leptris_attr_next(d)) {
2449
+ if (d->name_view.length == a->name_len &&
2450
+ d->name_view.data[0] ==
2451
+ scratch[a->name_off] &&
2452
+ memcmp(d->name_view.data,
2453
+ scratch + a->name_off,
2454
+ a->name_len) == 0) {
2455
+ dup = 1;
2456
+ break;
2457
+ }
2458
+ }
2459
+ if (dup) {
2460
+ if (doc) {
2461
+ extern struct LeptrisDiag* leptris_diag_emit(
2462
+ struct LeptrisDiag**, int*, int*,
2463
+ LeptrisDiagKind, LeptrisNodeRef,
2464
+ const char*, ...);
2465
+ char qn[130];
2466
+ size_t ql = a->name_len < 129
2467
+ ? a->name_len : 129;
2468
+ memcpy(qn, scratch + a->name_off, ql);
2469
+ qn[ql] = '\0';
2470
+ leptris_diag_emit(
2471
+ &doc->parse_diags,
2472
+ &doc->parse_diag_count,
2473
+ &doc->parse_diag_cap,
2474
+ LEPTRIS_DIAG_RECOVER, NULL,
2475
+ "Attribute %s redefined", qn);
2476
+ }
2477
+ continue;
2478
+ }
2479
+ if (!tail) {
2480
+ e->first_attribute_off =
2481
+ leptris_compact_int32_encode_inline(
2482
+ e, at, &e->first_attribute_off);
2483
+ } else {
2484
+ leptris_attr_set_next(tail, at);
2485
+ }
2486
+ tail = at;
2487
+ wired++;
2488
+ }
2489
+ e->attr_count = (uint8_t)wired;
2490
+ }
2491
+
2492
+ smap[i] = (LeptrisNode*)e;
2493
+ if (r->parent != IL_NONE) {
2494
+ pp.depth = (int)rdepth[r->parent] + 1;
2495
+ pp.last_child_stack[pp.depth - 1] = lc[r->parent];
2496
+ dp_wire_child(&pp,
2497
+ (LeptrisElement)smap[r->parent], (LeptrisNode*)e);
2498
+ lc[r->parent] = (LeptrisNode*)e;
2499
+ } else {
2500
+ doc->root = e;
2501
+ doc->new_dom_root = e;
2502
+ }
2503
+ } else {
2504
+ LeptrisTextNode* tn = &tblk[ntext++];
2505
+ const char* content = scratch + r->off;
2506
+ scratch[r->off + r->len] = '\0';
2507
+ tn->base.type = LEPTRIS_NODE_TYPE_TEXT;
2508
+ tn->base.frozen = 1;
2509
+ tn->base.line = r->line;
2510
+ tn->content = (char*)content;
2511
+ tn->content_len = r->len;
2512
+ tn->pool = pool;
2513
+ tn->borrowed = 1;
2514
+ tn->parent_off = 0;
2515
+ tn->next_sibling_off = 0;
2516
+ smap[i] = (LeptrisNode*)tn;
2517
+ pp.depth = (int)rdepth[r->parent] + 1;
2518
+ pp.last_child_stack[pp.depth - 1] = lc[r->parent];
2519
+ dp_wire_child(&pp,
2520
+ (LeptrisElement)smap[r->parent], (LeptrisNode*)tn);
2521
+ lc[r->parent] = (LeptrisNode*)tn;
2522
+ }
2523
+ }
2524
+
2525
+ /* doc-children chain: root (+ trailing top-level ws text) */
2526
+ {
2527
+ LeptrisNode* rn = smap[root_rec];
2528
+ doc->doc_children_head = rn;
2529
+ doc->doc_children_tail = rn;
2530
+ /* trailing doc-level ws: classic trims it after the loop —
2531
+ * not chained. (trail_off/trail_len retained for slices 3+
2532
+ * if the #580 chain parity ever needs it.) */
2533
+ (void)trail_off; (void)trail_len;
2534
+ }
2535
+
2536
+ leptris_root_doc_register(doc->root, doc);
2537
+ free(smap); free(rdepth); free(lc);
2538
+ return doc;
2539
+
2540
+ oom_pool:
2541
+ leptris_pool_destroy(pool);
2542
+ free(scratch);
2543
+ free(pristine);
2544
+ return NULL;
2545
+ }
2546
+ oom_early:
2547
+ free(scratch);
2548
+ free(pristine);
2549
+ return NULL;
2550
+ }
2551
+
2552
+ /* Public-gate: the interleaved lane try. NULL = bail to classic. */
2553
+ static struct leptris_document* dp_il_try(const char* xml, size_t len,
2554
+ int drop_ws, int keep_ent,
2555
+ int dtd_attrs) {
2556
+ /* Per-parse getenv (uncached): ~100-200ns against ~ms parses,
2557
+ * and it lets tests toggle the lane per-case in one process. */
2558
+ /* Empty string counts as unset: MSVC's _putenv_s(k, "") (the
2559
+ * only "unset" the CRT offers) leaves an empty value behind. */
2560
+ const char* gate = getenv("LEPTRIS_INTERLEAVED");
2561
+ if (!gate || !*gate) return NULL;
2562
+ if (keep_ent || dtd_attrs) return NULL;
2563
+ if (len == 0 || len >= 0x7FFFFFFFu) return NULL;
2564
+
2565
+ /* Scratch is built UP FRONT (slice 2): il_scan reads it with
2566
+ * the NUL-sentinel discipline, and dp_il_build reuses the same
2567
+ * buffer for the two-buffer model. */
2568
+ char* scratch = (char*)malloc(len + 65);
2569
+ if (!scratch) return NULL;
2570
+ memcpy(scratch, xml, len);
2571
+ memset(scratch + len, 0, 65);
2572
+
2573
+ IlCtx c;
2574
+ memset(&c, 0, sizeof(c));
2575
+ /* Slice 3a: upfront capacity from the input size — the growth
2576
+ * path started at 0/256 and a 13MB doc pays ~16 reallocs plus
2577
+ * the copy-overs. len/24 elems / len/48 attrs covers typical
2578
+ * shapes outright; pathological docs still grow. */
2579
+ c.crec = len / 24 + 64;
2580
+ c.recs = (IlRec*)malloc(c.crec * sizeof(IlRec));
2581
+ c.cattr = len / 48 + 32;
2582
+ c.attrs = (IlAttr*)malloc(c.cattr * sizeof(IlAttr));
2583
+ if (!c.recs || !c.attrs) {
2584
+ free(c.recs); free(c.attrs);
2585
+ free(scratch);
2586
+ return NULL;
2587
+ }
2588
+ uint32_t root;
2589
+ size_t toff, tlen;
2590
+ if (!il_scan(scratch, len, drop_ws, &c, &root, &toff, &tlen)) {
2591
+ free(c.recs); free(c.attrs);
2592
+ free(scratch);
2593
+ return NULL;
2594
+ }
2595
+ struct leptris_document* d =
2596
+ dp_il_build(scratch, len, &c, root, toff, tlen, drop_ws);
2597
+ free(c.recs); free(c.attrs);
2598
+ /* Uncached like the gate above: a cached stats flag keeps
2599
+ * printing after the env is unset (caught by the canary spec). */
2600
+ const char* stats = getenv("LEPTRIS_IL_STATS");
2601
+ if (stats && *stats)
2602
+ fprintf(stderr, "il: records=%zu attrs=%zu\n", c.nrec, c.nattr);
2603
+ return d;
2604
+ }
2605
+
1960
2606
  /* Public: copy the input then parse (standard path).
1961
2607
  *
1962
2608
  * The copy and the arena-sizing pre-scan are ONE fused SIMD pass
@@ -1966,6 +2612,10 @@ fail:
1966
2612
  * arena holds only nodes and strings. */
1967
2613
  struct leptris_document* direct_parse(const char* xml, size_t len) {
1968
2614
  if (!xml || len == 0) return NULL;
2615
+ {
2616
+ struct leptris_document* d = dp_il_try(xml, len, 0, 0, 0);
2617
+ if (d) return d;
2618
+ }
1969
2619
  return direct_parse_internal((char*)xml, len, 2, 0, 0, 0);
1970
2620
  }
1971
2621
 
@@ -1973,6 +2623,14 @@ struct leptris_document* direct_parse(const char* xml, size_t len) {
1973
2623
  struct leptris_document* direct_parse_flags(const char* xml, size_t len,
1974
2624
  unsigned parse_flags) {
1975
2625
  if (!xml || len == 0) return NULL;
2626
+ {
2627
+ struct leptris_document* d = dp_il_try(
2628
+ xml, len,
2629
+ (parse_flags & LEPTRIS_PARSE_DROP_WS_TEXT) ? 1 : 0,
2630
+ (parse_flags & LEPTRIS_PARSE_KEEP_ENTITY_REFS) ? 1 : 0,
2631
+ (parse_flags & LEPTRIS_PARSE_DTDATTR) ? 1 : 0);
2632
+ if (d) return d;
2633
+ }
1976
2634
  return direct_parse_internal((char*)xml, len, 2,
1977
2635
  (parse_flags & LEPTRIS_PARSE_DROP_WS_TEXT)
1978
2636
  ? 1 : 0,
@@ -720,6 +720,7 @@ typedef enum {
720
720
  XPATH_OP_MULTIPLY,
721
721
  XPATH_OP_DIV,
722
722
  XPATH_OP_MOD,
723
+ XPATH_OP_IDIV, /* XPath 2.0 integer division, truncating */
723
724
  XPATH_OP_UNION,
724
725
  XPATH_OP_NEGATION,
725
726
  /* XSLT 3.0 expression extensions (XPath 2.0+ forms), carried on