leptris 1.9.249.0 → 1.9.255.0

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
  5. data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
  6. data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
  7. data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
  8. data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
  9. data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
  10. data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
  11. data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
  12. data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
  13. data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
  14. data/lib/leptris/version.rb +1 -1
  15. data/vendor-src/README.md +1 -1
  16. data/vendor-src/libleptris/CHANGELOG.md +58 -0
  17. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  18. data/vendor-src/libleptris/benchmarks/CMakeLists.txt +15 -0
  19. data/vendor-src/libleptris/benchmarks/modification/bench_append_attr_pugixml.cpp +79 -0
  20. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +59 -10
  21. data/vendor-src/libleptris/src/leptris/html/html_parse.c +292 -316
  22. data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +14 -0
  23. data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +388 -10
  24. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +13 -40
  25. data/vendor-src/libleptris/src/leptris/xpath/parser.c +202 -2
  26. data/vendor-src/libleptris/src/leptris/xquery/xquery.c +15 -1
  27. data/vendor-src/libleptris/test/html/test_html.cpp +16 -0
  28. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +36 -13
  29. data/vendor-src/libleptris/test/xquery/test_xquery.cpp +171 -10
  30. data/vendor-src/libleptris/vcpkg.json +1 -1
  31. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4945b25ab8d7db217c541ca9a1ae2db6e7cfa3aea67584b7a7520ec0fcbf6e5
4
- data.tar.gz: b6a0355bceb4087bca19781de7ca02d1ad5fc35725685fba92e68591c0e5922a
3
+ metadata.gz: 5d1418101d40d96692273fa9327b6ad4c666a0705beeaa3c1500cdea623d3b83
4
+ data.tar.gz: f8605c3bd4a6f64e8a2361d4bdb375808665a57ec26f21deaf2d619a7695fa55
5
5
  SHA512:
6
- metadata.gz: abc9546e36bf7f05cbb384fd84afd2ecbf9442d11f4ad7d9d5f705230096797b24bbf20e4f8542ecaf28746427db98cc1551159701cec6f18b437f751e6592a7
7
- data.tar.gz: 13479600b858874e7f4f499eafb70cda0a97ec46f0248b0897e4878ca5a9e8ee84ca5a826cf5e4cccc7e2a27c95b554d6ce33e59a07270225f5a6c13ce5389a0
6
+ metadata.gz: 83c628648a66102c5a29da7f443761cf35c9b238328b22f9a46a4a43146e8a4b83a993aff7e53f1ca8b325abbe98c9f8c28a255ffb2a040ae050c91b01383a52
7
+ data.tar.gz: f831136e4a5fe977c2cd6ecc91ffd5e010f278fe0201f644d384ae6e1fa29aa4c887508a660c83fb5d87d38d8104f71e2656eab13876757a48c546e077065804
data/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.255.0] - 2026-09-26
9
+
10
+ ### Fixed
11
+
12
+ - Vendored libleptris 1.9.255 (riding 1.9.249-1.9.255, seven
13
+ engine syncs). Highlights:
14
+ - xquery: numeric-variable predicates are positional, not EBV —
15
+ (1)[1] keeps the atom, (1)[1] + 1 = 2 (#1392).
16
+ - xquery: document{...} materializes a real node (path steps
17
+ resolve over constructor results), the ! simple map is
18
+ item-preserving, multi-binding for/quantifier clauses parse,
19
+ and keyword constructors run the path continuation.
20
+ - xquery: F&O 3.0 numeric type promotion in value comparisons
21
+ and dedup (QT3 typed-atom lane) + timezone-normalized
22
+ xs:time comparison.
23
+ - html: implied-end pair checks run on tag ids with the tag
24
+ table gaps fixed (rb/rp/rt/rtc, option/optgroup) — round 2 of
25
+ the #1218 parse profile.
26
+
8
27
  ## [1.9.248.0] - 2026-09-26
9
28
 
10
29
  ### Fixed
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.248"
11
+ LIBLEPTRIS_VERSION = "1.9.255"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.249.0"
4
+ VERSION = "1.9.255.0"
5
5
  end
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.248
4
+ - libleptris v1.9.255
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,63 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.255] - 2026-09-26
4
+
5
+ <!-- Edit this section with the actual release notes. -->
6
+ <!-- See https://keepachangelog.com for format guidance. -->
7
+
8
+ ### Changed
9
+
10
+ - (describe changes here)
11
+
12
+
13
+ ## [1.9.254] - 2026-09-26
14
+
15
+ ### Added
16
+
17
+ - timezone-normalized xs:time comparison and ordering (xquery)
18
+
19
+
20
+
21
+ ## [1.9.253] - 2026-09-26
22
+
23
+ ### Fixed
24
+
25
+ - F&O numeric promotion in value comparison and distinct-values (xquery)
26
+
27
+
28
+
29
+ ## [1.9.252] - 2026-09-26
30
+
31
+ ### Added
32
+
33
+ - multi-binding for/quantifier clauses (xquery)
34
+
35
+
36
+
37
+ ## [1.9.251] - 2026-09-26
38
+
39
+ ### Added
40
+
41
+ - document{} materializes a node; simple map preserves items (xquery)
42
+
43
+
44
+
45
+ ## [1.9.250] - 2026-09-26
46
+
47
+ ### Fixed
48
+
49
+ - keyword ctors run the path continuation (document{...}/M) (xquery)
50
+
51
+
52
+
53
+ ## [1.9.249] - 2026-09-26
54
+
55
+ ### Performance
56
+
57
+ - classify implied-end pairs on tag ids (#1218) (html)
58
+
59
+
60
+
3
61
  ## [1.9.248] - 2026-09-26
4
62
 
5
63
  <!-- Edit this section with the actual release notes. -->
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.248
6
+ VERSION 1.9.255
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -211,6 +211,21 @@ endif()
211
211
  leptris_add_benchmark(bench_serialize serialization/bench_leptris_serialize.c)
212
212
  leptris_add_benchmark(bench_modify_leptris modification/bench_leptris_modify.c)
213
213
 
214
+ # Lane 18 (#1179): append + attr-parse vs pugixml (the lane bar is
215
+ # <=1x on both shapes). Needs C++ (pugixml.hpp) and the pugixml
216
+ # target; skipped when the dependency is absent.
217
+ if(PUGIXML_AVAILABLE)
218
+ add_executable(bench_append_attr_pugixml
219
+ modification/bench_append_attr_pugixml.cpp)
220
+ target_link_libraries(bench_append_attr_pugixml PRIVATE leptris::leptris)
221
+ if(PUGIXML_TARGET)
222
+ target_link_libraries(bench_append_attr_pugixml PRIVATE ${PUGIXML_TARGET})
223
+ else()
224
+ target_include_directories(bench_append_attr_pugixml PRIVATE ${PUGIXML_INCLUDE_DIR})
225
+ target_link_libraries(bench_append_attr_pugixml PRIVATE ${PUGIXML_LIBRARY})
226
+ endif()
227
+ endif()
228
+
214
229
  # XSLT release scorecard (TODO.xslt-full/13, #682): the transform
215
230
  # fixtures the perf lane gates on — best-of protocol, never mean.
216
231
  leptris_add_benchmark(bench_xslt_dispatch xslt/bench_dispatch.c utils.c)
@@ -0,0 +1,79 @@
1
+ // bench_append_attr_pugixml — Lane 18 (#1179): DOM append and
2
+ // attr-parse throughput vs pugixml. The lane bar is append <=1x and
3
+ // attr-parse <=1x against pugixml on these shapes.
4
+ #include "leptris.h"
5
+ #include "pugixml.hpp"
6
+ #include <chrono>
7
+ #include <cstdio>
8
+ #include <cstring>
9
+ #include <string>
10
+
11
+ static double now_ns() {
12
+ return (double)std::chrono::duration_cast<std::chrono::nanoseconds>(
13
+ std::chrono::steady_clock::now().time_since_epoch())
14
+ .count();
15
+ }
16
+
17
+ int main(void) {
18
+ const int N_APPEND = 200000;
19
+ const int N_PARSE = 20000;
20
+
21
+ /* ---- append: 200k element children on one root ---- */
22
+ {
23
+ double t0 = now_ns();
24
+ LeptrisDocument d = leptris_document_create();
25
+ LeptrisElement root = leptris_element_create(d, "root");
26
+ leptris_document_set_root(d, root);
27
+ for (int i = 0; i < N_APPEND; i++) {
28
+ LeptrisElement c = leptris_element_create(d, "c");
29
+ leptris_element_append_child(root, c);
30
+ }
31
+ double t1 = now_ns();
32
+ printf("append leptris : %8.1f ns/child\n",
33
+ (t1 - t0) / N_APPEND);
34
+ leptris_document_free(d);
35
+ }
36
+ {
37
+ double t0 = now_ns();
38
+ pugi::xml_document d;
39
+ pugi::xml_node root = d.append_child("root");
40
+ for (int i = 0; i < N_APPEND; i++)
41
+ root.append_child("c");
42
+ double t1 = now_ns();
43
+ printf("append pugixml : %8.1f ns/child\n",
44
+ (t1 - t0) / N_APPEND);
45
+ }
46
+
47
+ /* ---- attr-parse: parse 20k docs, 4 elems x 16 attrs ---- */
48
+ std::string xml = "<r>";
49
+ for (int e = 0; e < 4; e++) {
50
+ xml += "<e";
51
+ for (int a = 0; a < 16; a++)
52
+ xml += " a" + std::to_string(a) + "='value-" +
53
+ std::to_string(a) + "-xyz'";
54
+ xml += "/>";
55
+ }
56
+ xml += "</r>";
57
+ {
58
+ double t0 = now_ns();
59
+ for (int i = 0; i < N_PARSE; i++) {
60
+ LeptrisDocument d =
61
+ leptris_parse_string(xml.data(), xml.size(), NULL);
62
+ leptris_document_free(d);
63
+ }
64
+ double t1 = now_ns();
65
+ printf("attrparse leptris: %7.1f ns/doc (%zu bytes)\n",
66
+ (t1 - t0) / N_PARSE, xml.size());
67
+ }
68
+ {
69
+ double t0 = now_ns();
70
+ for (int i = 0; i < N_PARSE; i++) {
71
+ pugi::xml_document d;
72
+ d.load_buffer((void*)xml.data(), xml.size());
73
+ }
74
+ double t1 = now_ns();
75
+ printf("attrparse pugixml: %7.1f ns/doc\n",
76
+ (t1 - t0) / N_PARSE);
77
+ }
78
+ return 0;
79
+ }
@@ -108,6 +108,13 @@ typedef struct {
108
108
  * (the element cache line only needs touching twice per tag). */
109
109
  struct leptris_attribute* attr_cursor;
110
110
  struct leptris_attribute* attr_end;
111
+ /* #1200 round 2: per-element attr-name hash prefilter — the
112
+ * dup scan compares uint32 hashes (uint32 loop, no pointer
113
+ * chasing) and only walks the attr chain with memcmp on a hash
114
+ * hit. Pool-backed growth; reset per element. */
115
+ uint32_t* attr_dup_hashes;
116
+ size_t attr_dup_count;
117
+ size_t attr_dup_cap;
111
118
  /* Bulk text-node block (round 8): leptris_text_create_borrowed
112
119
  * is an out-of-line call per text node — pool_alloc alone costs
113
120
  * more than the field stores. Same carve pattern as the elem and
@@ -454,20 +461,56 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
454
461
  * (libxml2 recover semantics, and the digest's first-wins
455
462
  * becomes official); the duplicate never enters the chain, it
456
463
  * costs its already-carved slot. */
457
- if (elem->first_attribute_off != 0) {
464
+ {
465
+ uint32_t dh = 2166136261u;
466
+ for (size_t i = 0; i < name_len; i++) {
467
+ dh ^= (unsigned char)name[i];
468
+ dh *= 16777619u;
469
+ }
458
470
  int dup = 0;
459
- for (struct leptris_attribute* a =
460
- (struct leptris_attribute*)((char*)elem +
461
- elem->first_attribute_off);
462
- a; a = leptris_attr_next(a)) {
463
- if (a->name_view.length == attr->name_view.length &&
464
- a->name_view.data[0] == attr->name_view.data[0] &&
465
- memcmp(a->name_view.data, attr->name_view.data,
466
- attr->name_view.length) == 0) {
467
- dup = 1;
471
+ int cand = 0;
472
+ for (size_t i = 0; i < p->attr_dup_count; i++) {
473
+ if (p->attr_dup_hashes[i] == dh) {
474
+ cand = 1;
468
475
  break;
469
476
  }
470
477
  }
478
+ if (cand && elem->first_attribute_off != 0) {
479
+ /* hash hit: confirm against the real chain (collision
480
+ * or genuine duplicate) */
481
+ for (struct leptris_attribute* a =
482
+ (struct leptris_attribute*)(
483
+ (char*)elem + elem->first_attribute_off);
484
+ a; a = leptris_attr_next(a)) {
485
+ if (a->name_view.length ==
486
+ attr->name_view.length &&
487
+ a->name_view.data[0] ==
488
+ attr->name_view.data[0] &&
489
+ memcmp(a->name_view.data, attr->name_view.data,
490
+ attr->name_view.length) == 0) {
491
+ dup = 1;
492
+ break;
493
+ }
494
+ }
495
+ }
496
+ if (!dup) {
497
+ if (p->attr_dup_count == p->attr_dup_cap) {
498
+ size_t nc = p->attr_dup_cap ? p->attr_dup_cap * 2
499
+ : 16;
500
+ uint32_t* nh = (uint32_t*)leptris_pool_alloc(
501
+ p->pool, nc * sizeof(uint32_t));
502
+ if (nh) {
503
+ if (p->attr_dup_count)
504
+ memcpy(nh, p->attr_dup_hashes,
505
+ p->attr_dup_count *
506
+ sizeof(uint32_t));
507
+ p->attr_dup_hashes = nh;
508
+ p->attr_dup_cap = nc;
509
+ }
510
+ }
511
+ if (p->attr_dup_count < p->attr_dup_cap)
512
+ p->attr_dup_hashes[p->attr_dup_count++] = dh;
513
+ }
471
514
  if (dup) {
472
515
  if (p->doc) {
473
516
  extern struct LeptrisDiag* leptris_diag_emit(
@@ -489,6 +532,7 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
489
532
  * the raw-attr registration so BOTH surfaces agree. */
490
533
  return 1;
491
534
  }
535
+ if (elem->first_attribute_off != 0) {
492
536
  /* Cache should always be valid mid-parse. Fall back to walk
493
537
  * only if cache is NULL (defensive — shouldn't happen). */
494
538
  struct leptris_attribute* tail = p->current_elem_last_attr;
@@ -506,6 +550,7 @@ static inline int dp_add_attr_inline(DParser* p, LeptrisElement elem,
506
550
  * offset is ever needed. */
507
551
  elem->first_attribute_off =
508
552
  (int32_t)((char*)attr - (char*)elem);
553
+ }
509
554
  }
510
555
  p->current_elem_last_attr = attr;
511
556
  p->cur_attr_count++;
@@ -723,6 +768,7 @@ static int dp_parse_attrs(DParser* p, LeptrisElement elem) {
723
768
  p->current_elem_last_ns = NULL;
724
769
  p->raw_last = NULL;
725
770
  p->cur_attr_count = 0;
771
+ p->attr_dup_count = 0; /* hash array + cap persist across elements */
726
772
  /* Sentinel-terminated (parse endgame, third application): buf[len]
727
773
  * is NUL and NUL fails every classification below — not '>', not
728
774
  * '/', not '=' , not a quote, not IS_NAME_START. Each former
@@ -1333,6 +1379,9 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
1333
1379
  p.attr_capacity = attr_capacity;
1334
1380
  p.attr_cursor = attr_block;
1335
1381
  p.attr_end = attr_block + attr_capacity;
1382
+ p.attr_dup_hashes = NULL;
1383
+ p.attr_dup_count = 0;
1384
+ p.attr_dup_cap = 0;
1336
1385
  p.text_cursor = text_block;
1337
1386
  p.text_end = text_block + lt_count;
1338
1387
  p.cpi_cursor = cpi_block;