leptris 1.9.380.2-arm-linux → 1.9.382.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/Rakefile +9 -2
  4. data/ext/leptris/native/native.c +140 -14
  5. data/lib/leptris/version.rb +1 -1
  6. data/lib/leptris/xml/element.rb +11 -1
  7. data/lib/leptris/xml/ffi.rb +18 -0
  8. data/lib/leptris/xml/native.so +0 -0
  9. data/lib/leptris/xml/native_3_3.so +0 -0
  10. data/lib/leptris/xml/xsd.rb +54 -0
  11. data/lib/libleptris.so +0 -0
  12. data/vendor-src/README.md +1 -1
  13. data/vendor-src/libleptris/CHANGELOG.md +70 -0
  14. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  15. data/vendor-src/libleptris/TODO.SOTA.md +18 -2
  16. data/vendor-src/libleptris/TODO.max-perf/00-INDEX.md +4 -0
  17. data/vendor-src/libleptris/TODO.max-perf/15-c-face-vs-pugixml.md +87 -0
  18. data/vendor-src/libleptris/docs/MISSION-PLAN.md +1 -1
  19. data/vendor-src/libleptris/src/include/leptris.h +41 -0
  20. data/vendor-src/libleptris/src/leptris/common/simd_text.c +44 -0
  21. data/vendor-src/libleptris/src/leptris/common/simd_text.h +13 -0
  22. data/vendor-src/libleptris/src/leptris/common/simd_text_neon.c +51 -0
  23. data/vendor-src/libleptris/src/leptris/dom/element.c +31 -39
  24. data/vendor-src/libleptris/src/leptris/dom/element_modify.c +30 -10
  25. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +54 -13
  26. data/vendor-src/libleptris/src/leptris/leptris.c +12 -7
  27. data/vendor-src/libleptris/src/leptris/leptris_internal.h +7 -0
  28. data/vendor-src/libleptris/src/leptris/memory/arena.c +3 -2
  29. data/vendor-src/libleptris/src/leptris/rng/rng_regex.c +411 -17
  30. data/vendor-src/libleptris/src/leptris/xpath/evaluator_path.c +134 -4
  31. data/vendor-src/libleptris/src/leptris/xpath/vm.c +7 -7
  32. data/vendor-src/libleptris/src/leptris/xsd/compile.c +105 -2
  33. data/vendor-src/libleptris/src/leptris/xsd/content_model.c +45 -4
  34. data/vendor-src/libleptris/src/leptris/xsd/validate.c +129 -41
  35. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +24 -0
  36. data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +42 -3
  37. data/vendor-src/libleptris/src/leptris/xslt/xslt_pattern.c +16 -9
  38. data/vendor-src/libleptris/test/dom/test_dom.cpp +42 -0
  39. data/vendor-src/libleptris/test/parser/test_simd_count.cpp +68 -0
  40. data/vendor-src/libleptris/test/rng/test_rng.cpp +69 -1
  41. data/vendor-src/libleptris/test/xpath/test_xpath.cpp +8 -3
  42. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +310 -0
  43. data/vendor-src/libleptris/vcpkg.json +1 -1
  44. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db3ec4fdfeddcf213dff77dd14341bffd1dad711ea60877e08cdd0adad3fb05c
4
- data.tar.gz: cfff61372aebafdcb8911bfc554107cc5816a03beca575fc50c3c5640a08f5d0
3
+ metadata.gz: eeb080a56ba2f644ba7219077f866627006b2d5e8ea80405a86a17cff5853737
4
+ data.tar.gz: 8498da423561b35ddfabe900ae828df3ee9f9963c1498ac90cbb543f73c4c58e
5
5
  SHA512:
6
- metadata.gz: 0d11c5af4528032ee1b067bd7433dad8ad7b4f4cce9e986794ecf8f5075ab1f7307e349c8cc1e0b76b1d325f4792ba56fd75d0f043c8032f1a55572ab010e7f1
7
- data.tar.gz: 807a7f8407a86c141810f4ec074ef473ef8bdf846d3441e752239ccfaac27d0712de57c4fa628504e86bdcd63e19fef1ecc38d5650888e4e82531e09b2c916fa
6
+ metadata.gz: 750842a780c631c64e41614fa9f6b1a812bec0cffe9965fa868967a0b0375b16d9b1466279a07b990a90bbb0f14ee307c3868e9f4dd1702c1436f1af54d3d3f7
7
+ data.tar.gz: 16cfef2895a0d2b8ccfb5324492183111dacf30e4db0261f310418a3f2f97dbd6c68b24828858573ecd5062ba2ec9e3fa0f83c5904e8b769234dde45f9b7e86b
data/CHANGELOG.md CHANGED
@@ -1,7 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added — 1.9.382.0 (rides libleptris 1.9.382)
4
+
5
+ - **Schema#version** (moxml#460 item 2): the effective schema
6
+ version — "1.1" when the root declares `@version="1.1"` (1.1
7
+ semantics apply: `xs:alternative` CTA, `xs:assert`), else
8
+ "1.0". Engine face leptris_xsd_version (leptris/leptris#1849).
9
+ - **Native.name_equals** (leptris/leptris#1855 ask 2): engine-side
10
+ name comparison — no String materialization for name-comparing
11
+ consumers. Soft-gated; Qnil for non-element addresses.
12
+ - **Soft-attached faces** (leptris-ruby#548): version-gated symbols
13
+ no longer break the load on older engines; the symbol audit
14
+ exempts them until the pin reaches their release.
15
+
3
16
  ### Fixed — engine ride
4
17
 
18
+ - **Ride libleptris 1.9.382**: XSD structured error rows
19
+ (leptris/leptris#1843), negative wildcards (#1850), block +
20
+ category escapes in the pattern regex (#1853/#1856/#1857), the
21
+ parse-perf campaign work (#1844/#1847/#1848/#1851), and the
22
+ CAS-only retain spinlock.
23
+ - **Ride libleptris 1.9.380**: the xsi:type conformance fix
24
+ (leptris/leptris#1832, lutaml/moxml#433) — an element carrying
25
+ `xsi:type` is assessed against the NAMED type (XSD 1.0
26
+ §2.6.2/§3.3.4), so valid xsi:type-derived instances validate
27
+ instead of being rejected against the declared type's content
28
+ model. Released 1.9.380.0 with the pin still at 1.9.379 (the
29
+ vendored dylib missed the fix); this pin bump is the real ride.
30
+
5
31
  - **Ride libleptris 1.9.380**: the xsi:type conformance fix
6
32
  (leptris/leptris#1832, lutaml/moxml#433) — an element carrying
7
33
  `xsi:type` is assessed against the NAMED type (XSD 1.0
@@ -17,6 +43,14 @@
17
43
  parameters and marks all-native results; 1.9.380.1 shipped before
18
44
  the PR merged, so the face rides in 1.9.380.2.
19
45
 
46
+ ## [1.9.381.0] - 2026-10-22
47
+
48
+ Ride: libleptris 1.9.381 — the XSLT identity/copy-of quadratic fix
49
+ (#1839): identity transforms 13.0 s -> 57 ms on a 20k-book catalog;
50
+ every CLI row now beats xsltproc end-to-end. The Rakefile pin moves
51
+ 1.9.380 -> 1.9.381 in the same commit so the platform gems vendor
52
+ this core.
53
+
20
54
  ## [1.9.379.3] - 2026-10-22
21
55
 
22
56
  ### 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.380"
11
+ LIBLEPTRIS_VERSION = "1.9.382"
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
@@ -401,8 +401,15 @@ namespace :audit do
401
401
  .map { |n| n.sub(/\Aleptris_/, "") }.sort
402
402
  attached = File.read("lib/leptris/xml/ffi.rb")
403
403
  .scan(/attach_function :leptris_([a-z_0-9]+)/).flatten.sort
404
+ # Version-gated faces: soft-attached because engines BEFORE the
405
+ # named release lack the symbol (the load must survive them).
406
+ # They join the audit only when the engine pin reaches that
407
+ # release (the lockstep ride). TODO.concurrency/08-era law:
408
+ # never let one forward symbol poison the load.
409
+ version_gated = []
410
+ version_gated << "xsd_version" if Gem::Version.new(LIBLEPTRIS_VERSION) < Gem::Version.new("1.9.382")
404
411
  unattached = exported - attached
405
- unexported = attached - exported
412
+ unexported = attached - exported - version_gated
406
413
  unless unattached.empty?
407
414
  puts "exported but NOT attached (upstream surface drift):"
408
415
  unattached.each { |s| puts " leptris_#{s}" }
@@ -2999,18 +2999,41 @@ static void *create_elem_core(void *doc_ptr, void *parent,
2999
2999
  return elem;
3000
3000
  }
3001
3001
 
3002
+ /* leptris-ruby#539: the ATTACHED construction faces are
3003
+ * wrapper-visible mutations — they splice a built child under a
3004
+ * parent — so they run the same freed/readonly gates and version
3005
+ * bump as every other write face (bump before the call: a failed
3006
+ * mutation merely discards memos). The raw-address face keeps the
3007
+ * legacy Integer-document contract (moxml's pre-#340 callers pass
3008
+ * doc.c_address): a bare engine address cannot reach the Document
3009
+ * object, so the gate runs only when the caller names it. */
3002
3010
  static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
3003
3011
  VALUE parent_addr, VALUE name,
3004
3012
  VALUE attrs)
3005
3013
  {
3006
- void *doc_ptr = NIL_P(document)
3007
- ? NULL
3008
- : (void *)(uintptr_t)NUM2ULL(document);
3009
- void *parent = NIL_P(parent_addr)
3010
- ? NULL
3011
- : (void *)(uintptr_t)NUM2ULL(parent_addr);
3012
- void *elem = create_elem_core(doc_ptr, parent,
3013
- StringValueCStr(name), attrs);
3014
+ void *doc_ptr;
3015
+ void *parent;
3016
+ void *elem;
3017
+
3018
+ parent = NIL_P(parent_addr)
3019
+ ? NULL
3020
+ : (void *)(uintptr_t)NUM2ULL(parent_addr);
3021
+ if (!NIL_P(document) && !RB_INTEGER_TYPE_P(document)) {
3022
+ if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
3023
+ rb_raise(c_use_after_free_error,
3024
+ "owning document has been freed");
3025
+ /* The bump rides the ATTACH: a free-floating create mutates
3026
+ * no tree (detached-create discipline, no bump). */
3027
+ if (parent)
3028
+ document_advance_version(document);
3029
+ doc_ptr = doc_ptr_of(document);
3030
+ } else {
3031
+ doc_ptr = NIL_P(document)
3032
+ ? NULL
3033
+ : (void *)(uintptr_t)NUM2ULL(document);
3034
+ }
3035
+ elem = create_elem_core(doc_ptr, parent,
3036
+ StringValueCStr(name), attrs);
3014
3037
  return elem ? ULL2NUM((uintptr_t)elem) : Qnil;
3015
3038
  }
3016
3039
 
@@ -3025,15 +3048,29 @@ static VALUE nf_create_element_with_attrs_wrapped(
3025
3048
  VALUE self, VALUE document, VALUE parent_addr, VALUE name,
3026
3049
  VALUE attrs)
3027
3050
  {
3028
- void *doc_ptr = doc_ptr_of(document);
3029
- void *parent = NIL_P(parent_addr)
3030
- ? NULL
3031
- : (void *)(uintptr_t)NUM2ULL(parent_addr);
3032
- void *elem = create_elem_core(doc_ptr, parent,
3033
- StringValueCStr(name), attrs);
3051
+ void *doc_ptr;
3052
+ void *parent;
3053
+ void *elem;
3034
3054
  VALUE ck, key, node, cache;
3035
3055
 
3036
3056
  (void)self;
3057
+ parent = NIL_P(parent_addr)
3058
+ ? NULL
3059
+ : (void *)(uintptr_t)NUM2ULL(parent_addr);
3060
+ if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
3061
+ rb_raise(c_use_after_free_error,
3062
+ "owning document has been freed");
3063
+ /* #539: attached construction bumps the version — the same
3064
+ * discipline as append_binding_child (bump before the call; a
3065
+ * failed mutation merely discards memos) — so version-stamped
3066
+ * memos (C content, walk-reads) retire across the splice.
3067
+ * Free-floating creates (parent_addr 0) mutate no tree and
3068
+ * stay bump-free, like the detached create faces. */
3069
+ if (parent)
3070
+ document_advance_version(document);
3071
+ doc_ptr = doc_ptr_of(document);
3072
+ elem = create_elem_core(doc_ptr, parent,
3073
+ StringValueCStr(name), attrs);
3037
3074
  if (!elem) return Qnil;
3038
3075
  resolve_binding_classes();
3039
3076
  ck = doc_klass_for_kind(document, 0 /* element */);
@@ -3057,6 +3094,80 @@ static VALUE nf_create_element_with_attrs_wrapped(
3057
3094
  return node;
3058
3095
  }
3059
3096
 
3097
+ /* TODO.sota/04 (moxml build row): ONE construction crossing for the
3098
+ * consumer build shape — create + attrs + attach + optional text +
3099
+ * version bump + consumer mint, all in C off the PARENT WRAPPER's
3100
+ * struct (the document comes from the struct: no FFI twin, no
3101
+ * bridge, no per-element document climb on the Ruby side). Nokogiri's
3102
+ * equivalent shape is five separate crossings (Node.new, []= x2,
3103
+ * content=, add_child) + five wrapper allocs; this face is one
3104
+ * crossing + one wrapper. attrs arrives FLATTENED (name, value, ...)
3105
+ * like create_element_with_attrs_wrapped; text may be nil. */
3106
+ static VALUE nn_add_element_full(VALUE self, VALUE name, VALUE attrs,
3107
+ VALUE text)
3108
+ {
3109
+ struct native_node *pn;
3110
+ VALUE doc, ck, node;
3111
+ void *doc_ptr, *elem;
3112
+ int st;
3113
+
3114
+ resolve_binding_classes();
3115
+ TypedData_Get_Struct(self, struct native_node, &nn_type, pn);
3116
+ doc = pn->document;
3117
+ if (NIL_P(doc) || NIL_P(rb_ivar_get(doc, id_iv_c_address)))
3118
+ rb_raise(c_use_after_free_error,
3119
+ "owning document has been freed");
3120
+ if (rb_ivar_get(doc, id_iv_readonly) == Qtrue)
3121
+ rb_raise(c_readonly_error,
3122
+ "document is readonly — mutation attempted");
3123
+ /* Bump before the call: a failed construction merely discards
3124
+ * memos (the #539 discipline). */
3125
+ rb_ivar_set(doc, id_iv_version,
3126
+ LONG2FIX(FIX2LONG(rb_ivar_get(doc, id_iv_version)) + 1));
3127
+ doc_ptr = doc_ptr_of(doc);
3128
+ elem = create_elem_core(doc_ptr, pn->ptr,
3129
+ StringValueCStr(name), attrs);
3130
+ if (!elem) return Qnil;
3131
+ if (!NIL_P(text)) {
3132
+ st = f_elem_set_text(elem, StringValueCStr(text));
3133
+ check_status_c(st);
3134
+ }
3135
+ ck = doc_klass_for_kind(doc, 0 /* element */);
3136
+ if (!NIL_P(ck)) {
3137
+ node = mint_consumer_node(ck, doc, elem);
3138
+ /* The #508 identity contract, born-primed from the parent
3139
+ * wrapper: @native is the node itself and the contract state
3140
+ * (@context/@adapter) copies off self, so the consumer's
3141
+ * per-child wrap prime disappears from the build row. */
3142
+ rb_ivar_set(node, id_iv_mx_native, node);
3143
+ if (NIL_P(rb_ivar_get(node, id_iv_mx_context))) {
3144
+ VALUE cx = rb_ivar_get(self, id_iv_mx_context);
3145
+ if (!NIL_P(cx)) {
3146
+ VALUE ad = rb_ivar_get(self, id_iv_mx_adapter);
3147
+ rb_ivar_set(node, id_iv_mx_context, cx);
3148
+ if (!NIL_P(ad)) rb_ivar_set(node, id_iv_mx_adapter, ad);
3149
+ }
3150
+ }
3151
+ return node;
3152
+ }
3153
+
3154
+ node = rb_hash_aref(native_cache_of(doc),
3155
+ ULL2NUM((uint64_t)(uintptr_t)elem));
3156
+ if (!NIL_P(node)) return node;
3157
+ node = rb_obj_alloc(c_b_element);
3158
+ rb_iv_set(node, "@c_address", ULL2NUM((uint64_t)(uintptr_t)elem));
3159
+ rb_iv_set(node, "@document", doc);
3160
+ rb_iv_set(node, "@parent", Qnil);
3161
+ rb_iv_set(node, "@structure_memoizable", Qtrue);
3162
+ rb_iv_set(node, "@native_fast", Qtrue);
3163
+ rb_iv_set(node, "@pub_document", doc);
3164
+ rb_iv_set(node, "@addr_reads_fast", Qtrue);
3165
+ rb_iv_set(node, "@node_type", INT2FIX(0));
3166
+ rb_hash_aset(native_cache_of(doc),
3167
+ ULL2NUM((uint64_t)(uintptr_t)elem), node);
3168
+ return node;
3169
+ }
3170
+
3060
3171
  static VALUE nf_attribute_rows(VALUE self, VALUE document, VALUE addr)
3061
3172
  {
3062
3173
  (void)self;
@@ -3502,6 +3613,18 @@ static VALUE nf_name(VALUE self, VALUE addr)
3502
3613
  return s ? rb_utf8_str_new_cstr(s) : Qnil;
3503
3614
  }
3504
3615
 
3616
+ /* moxml#460/#1855 ask 2: engine-side name comparison — no String
3617
+ * materialization for name-comparing consumers. Qtrue/Qfalse;
3618
+ * Qnil when the address is not an element (mirrors nf_name). */
3619
+ static VALUE nf_name_equals(VALUE self, VALUE addr, VALUE name)
3620
+ {
3621
+ const char *s;
3622
+ (void)self;
3623
+ s = f_elem_name((void *)(uintptr_t)NUM2ULL(addr));
3624
+ if (!s) return Qnil;
3625
+ return strcmp(s, StringValueCStr(name)) == 0 ? Qtrue : Qfalse;
3626
+ }
3627
+
3505
3628
  static VALUE nf_element_text(VALUE self, VALUE addr)
3506
3629
  {
3507
3630
  const char *s;
@@ -3928,6 +4051,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3928
4051
  rb_define_alias(c_native_node, "text_read", "content");
3929
4052
  rb_define_alias(c_native_node, "name_read", "name");
3930
4053
  rb_define_method(c_native_node, "children", nn_children, 0);
4054
+ rb_define_method(c_native_node, "add_element_full",
4055
+ nn_add_element_full, 3);
3931
4056
  rb_define_alias(c_native_node, "children_read", "children");
3932
4057
  /* Unshadowed write face (the add_child_native pattern): the
3933
4058
  * registered Identity klasses shadow content= with the
@@ -3944,6 +4069,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
3944
4069
  m_native = rb_define_module_under(m_xml, "Native");
3945
4070
  m_native_sentinel = m_native;
3946
4071
  rb_define_module_function(m_native, "fast_name", nf_name, 1);
4072
+ rb_define_module_function(m_native, "name_equals", nf_name_equals, 2);
3947
4073
  id_sax_start_document = rb_intern("start_document");
3948
4074
  id_sax_end_document = rb_intern("end_document");
3949
4075
  id_sax_start_element = rb_intern("start_element");
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.380.2"
4
+ VERSION = "1.9.382.0"
5
5
  end
@@ -548,11 +548,17 @@ class Leptris::XML::Element < Leptris::XML::Node
548
548
  # Cross-document and docless (#376): full path first (lift +
549
549
  # engine copy), then the source original is removed; returns
550
550
  # the installed copy (self's new next sibling).
551
+ # #543: the raw-FFI lanes are wrapper-visible mutations too —
552
+ # they must advance the document version like the C-bound face,
553
+ # or every #530 walk-read memo warmed before the insert serves
554
+ # stale children afterwards (bump before the splice: a failed
555
+ # mutation merely discards memos).
551
556
  if Leptris::XML::Element.foreign_to?(node, @document)
552
557
  ensure_writable!
553
558
  unless Leptris::XML::Element.skip_adoption_lift?(node)
554
559
  Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
555
560
  end
561
+ @document&.advance_version
556
562
  Leptris::XML::FFI.check_status(
557
563
  Leptris::XML::FFI.leptris_element_insert_after(c_ptr, node.c_ptr))
558
564
  Leptris::XML::Node.invalidate_cross_document!(node, @document)
@@ -569,6 +575,7 @@ class Leptris::XML::Element < Leptris::XML::Node
569
575
  unless Leptris::XML::Element.skip_adoption_lift?(node)
570
576
  Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
571
577
  end
578
+ @document&.advance_version
572
579
  Leptris::XML::FFI.check_status(
573
580
  Leptris::XML::FFI.leptris_element_insert_after(c_ptr, node.c_ptr))
574
581
  node
@@ -580,12 +587,14 @@ class Leptris::XML::Element < Leptris::XML::Node
580
587
  # the namespace lift — the full path below handles it.
581
588
  # Cross-document and docless (#376): full path first (lift +
582
589
  # engine copy), then the source original is removed; returns
583
- # the installed copy (self's new previous sibling).
590
+ # the installed copy (self's new previous sibling). #543: the
591
+ # raw-FFI lanes advance the version like the C-bound face.
584
592
  if Leptris::XML::Element.foreign_to?(node, @document)
585
593
  ensure_writable!
586
594
  unless Leptris::XML::Element.skip_adoption_lift?(node)
587
595
  Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
588
596
  end
597
+ @document&.advance_version
589
598
  Leptris::XML::FFI.check_status(
590
599
  Leptris::XML::FFI.leptris_element_insert_before(c_ptr, node.c_ptr))
591
600
  Leptris::XML::Node.invalidate_cross_document!(node, @document)
@@ -602,6 +611,7 @@ class Leptris::XML::Element < Leptris::XML::Node
602
611
  unless Leptris::XML::Element.skip_adoption_lift?(node)
603
612
  Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
604
613
  end
614
+ @document&.advance_version
605
615
  Leptris::XML::FFI.check_status(
606
616
  Leptris::XML::FFI.leptris_element_insert_before(c_ptr, node.c_ptr))
607
617
  node
@@ -1397,6 +1397,24 @@ attach_function :leptris_parse_string,
1397
1397
  [:leptris_xsd_schema], :size_t
1398
1398
  attach_function :leptris_xsd_error,
1399
1399
  [:leptris_xsd_schema], UTF8_STRING
1400
+ # libleptris 1.9.382 (moxml#460/#1841): the structured report —
1401
+ # rows of {kind, component, line, column, message}; schema-
1402
+ # owned memory, valid until the next validate. out receives
1403
+ # the record array pointer; the return is the row count.
1404
+ attach_function :leptris_xsd_error_report,
1405
+ [:leptris_xsd_schema, :pointer], :size_t
1406
+ # libleptris 1.9.382 (moxml#460 item 2): the schema's
1407
+ # effective version — "1.1" when the root declares
1408
+ # @version="1.1" (1.1 semantics apply), else "1.0". SOFT
1409
+ # attach: engines before 1.9.382 lack the symbol, and a hard
1410
+ # attach would break every face at load for the whole gem —
1411
+ # callers gate on FFI.respond_to?(:leptris_xsd_version).
1412
+ begin
1413
+ attach_function :leptris_xsd_version,
1414
+ [:leptris_xsd_schema], UTF8_STRING
1415
+ rescue ::FFI::NotFoundError
1416
+ nil
1417
+ end
1400
1418
  # Slice 2 (libleptris 1.9.319, #1075): lexical validation.
1401
1419
  # Built-ins take the "xs:NAME" reference spelling; -1 = not
1402
1420
  # in the tier-1 table. simple_valid resolves a user
Binary file
Binary file
@@ -108,6 +108,20 @@ module Leptris::XML::XSD
108
108
  Leptris::XML::FFI.leptris_xsd_declaration_count(@handle)
109
109
  end
110
110
 
111
+ # The schema's effective version (libleptris 1.9.382, moxml#460
112
+ # item 2): "1.1" when the root carries @version="1.1" — the
113
+ # engine then applies 1.1 semantics (xs:alternative CTA,
114
+ # xs:assert) — else "1.0". Consumers scope 1.0-only checks
115
+ # against it.
116
+ def version
117
+ check_released!
118
+ unless Leptris::XML::FFI.respond_to?(:leptris_xsd_version)
119
+ raise Leptris::XML::Error,
120
+ 'Schema#version requires libleptris 1.9.382+ ' '(the leptris_xsd_version face)'
121
+ end
122
+ Leptris::XML::FFI.leptris_xsd_version(@handle) || '1.0'
123
+ end
124
+
111
125
  # The engine's schema-level error message, or nil when the
112
126
  # schema compiled cleanly.
113
127
  def error
@@ -150,6 +164,46 @@ module Leptris::XML::XSD
150
164
  end
151
165
  end
152
166
 
167
+ # The structured report (libleptris 1.9.382, moxml#460/#1841):
168
+ # one hash per finding — kind (element-decl / attribute-use /
169
+ # content-model / simpleType-facet / type-resolution /
170
+ # assertion / identity-constraint), component (declaring
171
+ # schema component), line, column, message (the same text
172
+ # validate_errors carries). Requires the 1.9.382 engine.
173
+ StructuredError = Struct.new(:kind, :component, :line, :column,
174
+ :message)
175
+
176
+ def structured_errors(document)
177
+ check_released!
178
+ valid?(document)
179
+ unless Leptris::XML::FFI.respond_to?(:leptris_xsd_error_report)
180
+ raise Leptris::XML::Error,
181
+ 'structured_errors requires libleptris 1.9.382+ ' \
182
+ '(the leptris_xsd_error_report face)'
183
+ end
184
+ out = ::FFI::MemoryPointer.new(:pointer)
185
+ n = Leptris::XML::FFI.leptris_xsd_error_report(@handle, out)
186
+ rows = out.get_pointer(0)
187
+ return [] if n.zero? || rows.null?
188
+
189
+ # C layout: kind* / component* / uint line / uint column /
190
+ # message* — 32 bytes per record with tail padding.
191
+ (0...n).map do |i|
192
+ ptr = rows + (i * 32)
193
+ kind = ptr.get_pointer(0)
194
+ component = ptr.get_pointer(8)
195
+ line = ptr.get_uint32(16)
196
+ column = ptr.get_uint32(20)
197
+ message = ptr.get_pointer(24)
198
+ StructuredError.new(
199
+ kind.nil? ? nil : kind.read_string,
200
+ component.nil? ? nil : component.read_string,
201
+ line, column,
202
+ message.nil? ? nil : message.read_string
203
+ )
204
+ end
205
+ end
206
+
153
207
  # Slice 3 direct face: a child SEQUENCE against a top-level
154
208
  # element declaration's compiled content model (occurrence
155
209
  # bounds, xs:any wildcards). +children+ are child Nodes (name
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.380
4
+ - libleptris v1.9.382
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,75 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.382] - 2026-10-26
4
+
5
+ ### Added
6
+
7
+ - category escapes — \p{L}, \p{Nd}, families (moxml#464) (xsd)
8
+ - codepoint-wide \p{Is...} blocks — the full F.1 table (moxml#464) (xsd)
9
+ - \p{Is...} block escapes in the pattern-facet regex (moxml#464) (xsd)
10
+ - XSD 1.1 negative wildcards — notQName on xs:any and xs:anyAttribute (moxml#460) (xsd)
11
+ - leptris_xsd_version — effective version reporting (moxml#460) (xsd)
12
+ - structured error rows — {kind, component, line, column, message} (xsd)
13
+
14
+ ### Fixed
15
+
16
+ - gate the category table on LEPTRIS_HAS_UTF8PROC (simd)
17
+ - CAS-only retain spinlock — the plain spin read raced the atomics (memory)
18
+ - dispatch_init fully initializes the count3_high slot on every arch (simd)
19
+ - declared non-UTF-8 encodings are EXEMPT from the fused gate (parse)
20
+
21
+ ### Performance
22
+
23
+ - parse_string adopts the conversion buffer — the copy-in chain and the pristine carve drop out (#1846)
24
+ - raise the element-index build threshold to 8 axis queries (xpath)
25
+ - fuse the UTF-8 fast gate into the count3 sizing pass (parse)
26
+ - rank unmutated documents by parse-time byte offset (xpath)
27
+ - pre-size the element-position journal from the count3 estimate (parse)
28
+
29
+
30
+
31
+ ## [1.9.381] - 2026-10-22
32
+
33
+ The XSLT transform engine is no longer slower than libxslt on the
34
+ workload users actually run. The identity transform
35
+ (`match="@*|node()"` + `xsl:copy`) was quadratic: 13.0 s where
36
+ xsltproc takes 65 ms on a 20k-book catalog. Three independent causes
37
+ went in PR #1839, and every measured CLI row now beats xsltproc
38
+ end-to-end:
39
+
40
+ | stylesheet (20k-book catalog) | xsltproc 1.1.35 | 1.9.380 | 1.9.381 |
41
+ | --- | --- | --- | --- |
42
+ | identity `@*|node()` | 64.5 ms | 13,002 ms | **56.9 ms** |
43
+ | `copy-of //book` | 37.2 ms | 58.8 ms | **25.3 ms** |
44
+ | dispatch (apply-templates) | 63.8 ms | 23.5 ms | **23.6 ms** |
45
+ | 1,000-template sheet | 293.8 ms | 18.7 ms | **17.6 ms** |
46
+
47
+ ### Fixed
48
+
49
+ - apply xsi:type at every depth in whole-instance validation: an
50
+ instance document may pin a derived type on any element via
51
+ `xsi:type`; validation previously honored it only at the root, so
52
+ deeper particles validated against the declared type (#1838)
53
+
54
+ ### Performance
55
+
56
+ - **xslt: identity transforms and copy-of were quadratic (#1839).**
57
+ Three fixes: (1) the pattern fast path never compiled node-type
58
+ tests — `node(`/`text(`/`processing-instruction(` parsed as NCName
59
+ and failed on the paren, sending every match through the quadratic
60
+ ancestor ladder (and `processing-instruction(` compared 21 of its
61
+ 23 prefix characters); (2) the document-order rank table hashed
62
+ pointers by low bits, which degenerates on fixed-stride pool
63
+ allocations (872 ms to build for a 500k-node transform); (3) the
64
+ append-tail pair pointed at the deepest node a body built, so wide
65
+ parents re-walked their whole child chain on every append after an
66
+ interleaved child. Full suite 2103/2103, libxslt-205 output parity
67
+ included.
68
+ - dom: lane-18 append/create micro-levers from a fresh profile
69
+ (#1836)
70
+
71
+
72
+
3
73
  ## [1.9.380] - 2026-10-22
4
74
 
5
75
  ### Fixed
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.380
6
+ VERSION 1.9.382
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -106,7 +106,11 @@ Harness `wt/460-harness/build_bench.rb`; A/B pairs alternate within
106
106
  ONE window (drift ±20%). Rejected within noise: fast_attach,
107
107
  native-cache deletion, doc_klass memo, identity-probe deletion,
108
108
  add_child alias. Residual: wrapper allocation (owner-level node
109
- layout).
109
+ layout). **Allocation census 2026-10-22: AT FLOOR** — moxml
110
+ 0.06 minor GC/build vs nokogiri 0.39 (6.5x fewer), 3420 objects vs
111
+ 9907; every named suspect eliminated by the levers; the non-floor
112
+ remainder is 17 objects/build (~0.1% CPU). No lever remains — the
113
+ gap is the wrapper-count/identity-contract decision.
110
114
  - [ ] per-element object budget: one Ruby object per created node
111
115
  (drop the shell entirely post-create-priming; the mint cache
112
116
  insert policy; the context registration)
@@ -138,7 +142,19 @@ three interleaved-median runs (`wt/460-harness/pipeline_bench.rb`,
138
142
  build, serialize medians, fail on >5% regression)
139
143
 
140
144
  ### B5 remaining red rows
141
- - [ ] pugixml lane-18 append/attr-parse (2.0x/5.9x; hash prefilter)
145
+ - [x] pugixml lane-18 append/attr-parse — **re-dated 2026-10-22: the
146
+ 2.0x/5.9x rows no longer exist.** The named levers shipped in
147
+ the prior 22-round campaign (dup-check hash PR #1413, FNV-fused
148
+ lookup PR #1425, append parity PR #1533). Attr-lookup
149
+ ~parity (0.99x; caveat: in-process rigs let leptris's teardown
150
+ depress the adjacent pugixml row — self-warmed rigs are the
151
+ honest numbers). Append: **1.44x on self-warmed rigs; 1.37x
152
+ after PR leptris#1836** (4 behavior-identical levers: childless-
153
+ proof ancestor-walk skip, fused type chains, bounded name scan,
154
+ create_pooled double-copy fix; −3.6%, 9/10 pairs). Residual
155
+ ~1.37x parse gap = conformance features pugixml doesn't do
156
+ (dup detection, error positions, NUL writes) — the measured
157
+ floor; owner decision in #1436.
142
158
  - [ ] zero-copy text lever (#1218) — adopt if it wins any row
143
159
 
144
160
  ## Gate C — Safety: trust is auditable
@@ -28,3 +28,7 @@ TODO.max-perf/node-layout-fork.md — 5 slices, addresses #1222 +
28
28
  The interleaved-parse rewrite (owner-directed 2026-09-20):
29
29
  TODO.max-perf/interleaved-parse.md — the #1222 step-change; slices 0-4,
30
30
  each CI-benchmark-gated.
31
+
32
+ The C-face vs pugixml campaign (serialbench fair-flags wave, 2026-10-24):
33
+ TODO.max-perf/15-c-face-vs-pugixml.md — parse 2.81x / generation 2.23x /
34
+ xpath 3.48x; three tracks, CI-bench-gated. Track A1 shipped (PR #1844).