leptris 1.9.249.0-arm-linux → 1.9.255.1-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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5d9d59022a553eb806991fc47891ebe5e775eae726bb7a4c0987513bd585e41
4
- data.tar.gz: 4e5f8c02241089ec82b643489b453f7fd9347db0076bd4134102a84ade71f917
3
+ metadata.gz: 7b4e0a9650a8476f55d7168393310d84a0f0bc5c4bc68afbcb8da52eb4461b88
4
+ data.tar.gz: faeaa46310e9c4034055c61971d3129a0beb92e0aed1dfb3fdb316e40c5a9b83
5
5
  SHA512:
6
- metadata.gz: c69e15c543bf77c4f0bb32bfb012194a08873bbb433832f783ecd652b8b316709a5ee8cc805aae483f2636c467e4d83ab206e3a637ce18e1305e010e6b453a56
7
- data.tar.gz: 3b62bb55637f6db12c7c309fa3658a085c928afd4e5939a30c2261f29bb763d2980acf7afaf55c57783af2c0adfc4cba21bcf6939d50db1d7d57116691b1bfc5
6
+ metadata.gz: 6b96a662e7a06ecb95d7a5545a7c69d91c071738362837a061017ccccf1fd11e56b05594e2a42b15fb1fe6e1c9e10bc5fc34cc3d668dc35c1d580d2694c0bdca
7
+ data.tar.gz: f8ff48b9d740b085b7945cf5a0a9a2a2ccd9582589a8331e86b7f56d9a4226ce014a642bdb4b1141770b51fa00e92036496dd19d19230b7573e84478778265c0
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.1"
5
5
  end
@@ -31,33 +31,33 @@ class Leptris::XML::PlanValue
31
31
  end
32
32
 
33
33
  def kind
34
- inc_crossing
34
+ @counter[0] += 1
35
35
  VALUE_KINDS.fetch(Leptris::XML::FFI.leptris_plan_value_kind(@ptr))
36
36
  end
37
37
 
38
38
  # wire_name of the plan row that produced this value.
39
39
  def name
40
- inc_crossing
40
+ @counter[0] += 1
41
41
  Leptris::XML::FFI.leptris_plan_value_name(@ptr)
42
42
  end
43
43
 
44
44
  # Host type_tag echoed verbatim (0 when the row had none).
45
45
  def type_tag
46
- inc_crossing
46
+ @counter[0] += 1
47
47
  Leptris::XML::FFI.leptris_plan_value_type_tag(@ptr)
48
48
  end
49
49
 
50
50
  # #1273: LEPTRIS_NODE_TYPE_* of this value's source node (0 for
51
51
  # ELEMENT/COLLECTION wrappers and synthesized text).
52
52
  def node_kind
53
- inc_crossing
53
+ @counter[0] += 1
54
54
  Leptris::XML::FFI.leptris_plan_value_node_kind(@ptr)
55
55
  end
56
56
 
57
57
  # #1273: dense sibling rank inside the producing element (0 if
58
58
  # unranked) — document-order identity without byte offsets.
59
59
  def order_index
60
- inc_crossing
60
+ @counter[0] += 1
61
61
  Leptris::XML::FFI.leptris_plan_value_order_index(@ptr)
62
62
  end
63
63
 
@@ -85,26 +85,26 @@ class Leptris::XML::PlanValue
85
85
 
86
86
  # SCALAR / RAW / CALLBACK string value.
87
87
  def string_value
88
- inc_crossing
88
+ @counter[0] += 1
89
89
  Leptris::XML::FFI.leptris_plan_value_string(@ptr)
90
90
  end
91
91
 
92
92
  # CALLBACK: document byte offset of the source node (0 unknown).
93
93
  def position
94
- inc_crossing
94
+ @counter[0] += 1
95
95
  Leptris::XML::FFI.leptris_plan_value_position(@ptr)
96
96
  end
97
97
 
98
98
  # ELEMENT: child-value count. COLLECTION: item count.
99
99
  def count
100
- inc_crossing
100
+ @counter[0] += 1
101
101
  Leptris::XML::FFI.leptris_plan_value_count(@ptr)
102
102
  end
103
103
 
104
104
  # ELEMENT child value / COLLECTION item at +i+ (nil out of
105
105
  # range). Borrowed: the owning root must stay alive.
106
106
  def at(i)
107
- inc_crossing
107
+ @counter[0] += 1
108
108
  ptr = Leptris::XML::FFI.leptris_plan_value_at(@ptr, i)
109
109
  return nil if ptr.null?
110
110
  child_plan = child_plan_for_position(ptr)
@@ -114,7 +114,7 @@ class Leptris::XML::PlanValue
114
114
 
115
115
  # ELEMENT: attribute value by wire_name (nil when absent).
116
116
  def attribute(wire_name)
117
- inc_crossing
117
+ @counter[0] += 1
118
118
  Leptris::XML::FFI.leptris_plan_value_attribute(@ptr, wire_name.to_s)
119
119
  end
120
120
 
@@ -149,7 +149,7 @@ class Leptris::XML::PlanValue
149
149
  # (0 when absent). Collection rows appear as Arrays; callbacks
150
150
  # are emitted as +{value:, position:, type_tag:}+ like #to_ruby.
151
151
  def as_kwarg_hash
152
- inc_crossing # the kind probe + name + type_tag are one logical fetch
152
+ @counter[0] += 1 # the kind probe + name + type_tag are one logical fetch
153
153
  case kind
154
154
  when :element
155
155
  {
@@ -194,9 +194,6 @@ class Leptris::XML::PlanValue
194
194
 
195
195
  private
196
196
 
197
- def inc_crossing
198
- @counter[0] += 1
199
- end
200
197
 
201
198
  def kwarg_attributes
202
199
  ((@plan && @plan[:attributes]) || []).each_with_object({}) do |row, result|
@@ -290,7 +287,7 @@ class Leptris::XML::PlanValue
290
287
  rows = @plan[:children] || []
291
288
  row = rows.size == 1 ? rows.first : nil
292
289
  unless row
293
- inc_crossing
290
+ @counter[0] += 1
294
291
  child_name = Leptris::XML::FFI.leptris_plan_value_name(ptr)
295
292
  row = rows.find { |r| r[:name] == child_name }
296
293
  end
data/lib/libleptris.so CHANGED
Binary file
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;