leptris 1.9.385.0-ppc64le-linux → 1.9.388.0-ppc64le-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 +4 -4
- data/CHANGELOG.md +11 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +11 -1
- data/lib/leptris/xml/element.rb +15 -10
- data/lib/leptris/xml/ffi.rb +42 -0
- data/lib/leptris/xml/xsd.rb +35 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +163 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/benchmarks/parse/arena_ab.sh +40 -0
- data/vendor-src/libleptris/benchmarks/parse/bench_parse_ab.cpp +48 -1
- data/vendor-src/libleptris/src/include/leptris/descriptor.h +16 -0
- data/vendor-src/libleptris/src/include/leptris.h +77 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.c +33 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.h +17 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text_avx2.c +50 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text_neon.c +52 -0
- data/vendor-src/libleptris/src/leptris/descriptor/plan.c +82 -0
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +23 -2
- data/vendor-src/libleptris/src/leptris/dom/element_query.c +201 -0
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +24 -38
- data/vendor-src/libleptris/src/leptris/memory/arena.c +72 -20
- data/vendor-src/libleptris/src/leptris/memory/pool.c +27 -0
- data/vendor-src/libleptris/src/leptris/memory/pool.h +5 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_axes.c +15 -0
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +232 -15
- data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +180 -46
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +69 -22
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +10 -0
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +201 -0
- data/vendor-src/libleptris/test/common/test_simd_text.cpp +137 -0
- data/vendor-src/libleptris/test/dom/test_document_children.cpp +256 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +263 -0
- data/vendor-src/libleptris/test/memory/test_arena.cpp +38 -0
- data/vendor-src/libleptris/test/xsd/libxml2-cases/schema_set/f.xsd +13 -0
- data/vendor-src/libleptris/test/xsd/libxml2-cases/schema_set/g.xsd +9 -0
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +293 -1
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 238b6117d6d53b2e74db8530678f08b444e405ed9569cc0d5c794bc1539de2db
|
|
4
|
+
data.tar.gz: 6e3a5e2de83a00123bc4ea19fd4818526809f05248b41e0aa6d7a9d9532d71a2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae6c2a635dce0825ae7f0d85ecfaf343a912f551ce7a88e62f5686de2dbfa1ed736aa8c512a3c33ffa3ab236de554ff1ac7ecc316d419cda9bf2e7ec6c68db91
|
|
7
|
+
data.tar.gz: d96b043f56180f0fc6c79a9a25c1678d1161d099b544ed0ba4ebfa5a220fec998b287b591d6535305d3a42b5bb7eada48576f0276ea930b4cfc5db4313589da5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
### Changed — 1.9.388.0 (rides libleptris 1.9.388)
|
|
4
|
+
|
|
5
|
+
- Engine pin 1.9.386 -> 1.9.388: carries the one-pass text-run scan
|
|
6
|
+
(leptris/leptris#1895, #1842 — text shapes 764-804 -> 1370-1448 MB/s,
|
|
7
|
+
pugixml gap 2.9x -> 1.70x) and the arena retain-pool size-class split
|
|
8
|
+
(leptris/leptris#1893, #1846), plus the 1.9.387 wave (ns-declaration
|
|
9
|
+
snapshot, packed op-blob build, children_batch + name_compare,
|
|
10
|
+
XSD skip-wildcards / attributeGroup order / length facets / builtin
|
|
11
|
+
lexical table / schema-set compile_set / anyType default — the
|
|
12
|
+
schema-set faces this pin un-gates in spec/xml/xsd_spec.rb).
|
|
13
|
+
|
|
3
14
|
### Added — 1.9.382.0 (rides libleptris 1.9.382)
|
|
4
15
|
|
|
5
16
|
- **Schema#version** (moxml#460 item 2): the effective schema
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.388"
|
|
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
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -692,7 +692,17 @@ class Leptris::XML::Document
|
|
|
692
692
|
def absorbed?
|
|
693
693
|
!@absorbed_into.nil?
|
|
694
694
|
end
|
|
695
|
-
|
|
695
|
+
|
|
696
|
+
# leptris#1866: cross-document adoption lifetime anchor — the
|
|
697
|
+
# target document keeps the source alive so no wrapper/cache can
|
|
698
|
+
# outlive the source arena (standoc svg cleanup crash).
|
|
699
|
+
def retain_source_document!(source)
|
|
700
|
+
return unless source.is_a?(Leptris::XML::Document)
|
|
701
|
+
return if source.equal?(self)
|
|
702
|
+
(@retained_sources ||= []) << source
|
|
703
|
+
source
|
|
704
|
+
end
|
|
705
|
+
|
|
696
706
|
def free
|
|
697
707
|
return if @freed.state == :freed
|
|
698
708
|
@freed.state = :freed
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -494,16 +494,19 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
494
494
|
prefix = key.start_with?("xmlns:") ? key.delete_prefix("xmlns:") : nil
|
|
495
495
|
node.add_namespace_definition(prefix, uri)
|
|
496
496
|
end
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
# is
|
|
500
|
-
#
|
|
501
|
-
# declarations
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
497
|
+
# leptris#1866: the own-declaration PRUNE that used to live here
|
|
498
|
+
# (libxml2 reparent parity, moxml #208 lineage) is REMOVED — it
|
|
499
|
+
# is unsound on this engine. The engine's cross-document deep
|
|
500
|
+
# copy resolves each copied element's namespace through the
|
|
501
|
+
# SOURCE's in-scope declarations at copy time: pruning an own
|
|
502
|
+
# declaration made every descendant copy lose its namespace URI
|
|
503
|
+
# (adopted svgs fell out of the SVG namespace; classupdate's
|
|
504
|
+
# namespaced queries then matched nothing and the attribute
|
|
505
|
+
# write path corrupted a sibling attr name — <use =""> and the
|
|
506
|
+
# standoc svg-cleanup segfaults). libxml2 gets away with the
|
|
507
|
+
# prune because its nodes keep per-node URIs on reparent; the
|
|
508
|
+
# redundant xmlns in the serialized copy is the correct output
|
|
509
|
+
# here.
|
|
507
510
|
nil
|
|
508
511
|
end
|
|
509
512
|
|
|
@@ -559,6 +562,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
559
562
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
|
|
560
563
|
end
|
|
561
564
|
@document&.advance_version
|
|
565
|
+
@document&.retain_source_document!(node.document)
|
|
562
566
|
Leptris::XML::FFI.check_status(
|
|
563
567
|
Leptris::XML::FFI.leptris_element_insert_after(c_ptr, node.c_ptr))
|
|
564
568
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
|
@@ -595,6 +599,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
595
599
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces_for_sibling)
|
|
596
600
|
end
|
|
597
601
|
@document&.advance_version
|
|
602
|
+
@document&.retain_source_document!(node.document)
|
|
598
603
|
Leptris::XML::FFI.check_status(
|
|
599
604
|
Leptris::XML::FFI.leptris_element_insert_before(c_ptr, node.c_ptr))
|
|
600
605
|
Leptris::XML::Node.invalidate_cross_document!(node, @document)
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -442,6 +442,16 @@ module Leptris
|
|
|
442
442
|
PLAN_OP_END = 3
|
|
443
443
|
attach_function :leptris_plan_result_build,
|
|
444
444
|
[:leptris_plan, :pointer, :size_t, :pointer], :leptris_plan_result
|
|
445
|
+
# libleptris 1.9.387: the packed-blob lane of the same face
|
|
446
|
+
# (five uint32 per op plus name/value bytes). SOFT attach —
|
|
447
|
+
# engines before 1.9.387 lack the symbol.
|
|
448
|
+
begin
|
|
449
|
+
attach_function :leptris_plan_result_build_packed,
|
|
450
|
+
[:leptris_plan, :pointer, :size_t, :pointer],
|
|
451
|
+
:leptris_plan_result
|
|
452
|
+
rescue ::FFI::NotFoundError
|
|
453
|
+
nil
|
|
454
|
+
end
|
|
445
455
|
|
|
446
456
|
# libleptris 1.9.312 (#1551): serialize a walk result back
|
|
447
457
|
# to XML guided by its plan — the plan supplies element
|
|
@@ -750,6 +760,26 @@ attach_function :leptris_parse_string,
|
|
|
750
760
|
[:leptris_element, :pointer, :pointer, :size_t], :size_t
|
|
751
761
|
attach_function :leptris_element_children,
|
|
752
762
|
[:leptris_element, :pointer, :size_t], :size_t
|
|
763
|
+
# libleptris 1.9.387 engine faces not yet wrapped in Ruby —
|
|
764
|
+
# attached so the export audit stays in lockstep; adoption
|
|
765
|
+
# wraps them (leptris#1855 batch children: (addr, kind) pairs,
|
|
766
|
+
# document order only when cap >= count; #1874 namespace
|
|
767
|
+
# snapshot: one crossing per subtree, rows are seven uint32
|
|
768
|
+
# (depth, name_count, names_off, prefix_off, prefix_len,
|
|
769
|
+
# href_off, href_len) into a caller blob, 0xFFFFFFFF prefix_off
|
|
770
|
+
# = default-ns declaration; #1855 ask-2 name compare: strcmp
|
|
771
|
+
# semantics, non-elements compare as the empty string).
|
|
772
|
+
begin
|
|
773
|
+
attach_function :leptris_element_children_batch,
|
|
774
|
+
[:pointer, :pointer, :pointer, :size_t], :size_t
|
|
775
|
+
attach_function :leptris_node_name_compare,
|
|
776
|
+
[:pointer, :string, :size_t], :int
|
|
777
|
+
attach_function :leptris_element_namespace_declarations_snapshot,
|
|
778
|
+
[:leptris_element, :pointer, :size_t, :pointer,
|
|
779
|
+
:pointer, :size_t, :pointer], :size_t
|
|
780
|
+
rescue ::FFI::NotFoundError
|
|
781
|
+
nil
|
|
782
|
+
end
|
|
753
783
|
attach_function :leptris_node_as_element,
|
|
754
784
|
[:leptris_node_ref], :leptris_element
|
|
755
785
|
attach_function :leptris_element_as_node,
|
|
@@ -1391,6 +1421,18 @@ attach_function :leptris_parse_string,
|
|
|
1391
1421
|
# handles, exactly like the string face.
|
|
1392
1422
|
attach_function :leptris_xsd_compile_file,
|
|
1393
1423
|
[:string, :pointer], :leptris_xsd_schema
|
|
1424
|
+
# libleptris 1.9.387 (leptris#1881): compile a SET of schema
|
|
1425
|
+
# documents (NULL-terminated char* array) into one schema —
|
|
1426
|
+
# imports resolve across the whole set, so substitution-group
|
|
1427
|
+
# members declared in importing documents are visible to the
|
|
1428
|
+
# head's content model. SOFT attach: engines before 1.9.387
|
|
1429
|
+
# lack the symbol.
|
|
1430
|
+
begin
|
|
1431
|
+
attach_function :leptris_xsd_compile_set,
|
|
1432
|
+
[:pointer, :size_t, :pointer], :leptris_xsd_schema
|
|
1433
|
+
rescue ::FFI::NotFoundError
|
|
1434
|
+
nil
|
|
1435
|
+
end
|
|
1394
1436
|
attach_function :leptris_xsd_free,
|
|
1395
1437
|
[:leptris_xsd_schema], :void
|
|
1396
1438
|
attach_function :leptris_xsd_declaration_count,
|
data/lib/leptris/xml/xsd.rb
CHANGED
|
@@ -16,6 +16,12 @@ module Leptris::XML::XSD
|
|
|
16
16
|
Schema.compile_file(path)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Compile a set of schema documents (paths) into one schema
|
|
20
|
+
# (libleptris >= 1.9.387): imports resolve across the whole set.
|
|
21
|
+
def compile_set(paths)
|
|
22
|
+
Schema.compile_set(paths)
|
|
23
|
+
end
|
|
24
|
+
|
|
19
25
|
# Slice 2 (libleptris 1.9.319, #1075): built-in lexical table.
|
|
20
26
|
# +builtin+ takes the "xs:NAME" reference spelling. Returns
|
|
21
27
|
# true/false; an unknown table name raises — a silent false
|
|
@@ -101,6 +107,35 @@ module Leptris::XML::XSD
|
|
|
101
107
|
new(Handle.new(raw))
|
|
102
108
|
end
|
|
103
109
|
|
|
110
|
+
# Compile a SET of schema documents (paths) into one schema:
|
|
111
|
+
# imports resolve across the whole set, so components declared
|
|
112
|
+
# in importing documents (substitution-group members, derived
|
|
113
|
+
# types) are visible to the head's content models — the
|
|
114
|
+
# one-document compile cannot see them (libleptris 1.9.387,
|
|
115
|
+
# leptris#1881). Requires the leptris_xsd_compile_set face.
|
|
116
|
+
def self.compile_set(paths)
|
|
117
|
+
paths = paths.map { |p| p.to_s }
|
|
118
|
+
raise ArgumentError, "schema set needs at least one path" if paths.empty?
|
|
119
|
+
unless Leptris::XML::FFI.respond_to?(:leptris_xsd_compile_set)
|
|
120
|
+
raise Leptris::XML::Error,
|
|
121
|
+
"Schema.compile_set requires libleptris 1.9.387+ " \
|
|
122
|
+
"(the leptris_xsd_compile_set face)"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
strings = paths.map { |p| ::FFI::MemoryPointer.from_string(p) }
|
|
126
|
+
array = ::FFI::MemoryPointer.new(:pointer, strings.size)
|
|
127
|
+
array.write_array_of_pointer(strings)
|
|
128
|
+
status = ::FFI::MemoryPointer.new(:int)
|
|
129
|
+
raw = Leptris::XML::FFI.leptris_xsd_compile_set(array, strings.size,
|
|
130
|
+
status)
|
|
131
|
+
if raw.null?
|
|
132
|
+
raise Leptris::XML::Error,
|
|
133
|
+
"schema set compile failed (status #{status.read_int}): " \
|
|
134
|
+
"#{paths.join(', ')}"
|
|
135
|
+
end
|
|
136
|
+
new(Handle.new(raw))
|
|
137
|
+
end
|
|
138
|
+
|
|
104
139
|
# Number of top-level schema declarations the compiler
|
|
105
140
|
# recognized (elements, types, groups, ...).
|
|
106
141
|
def declaration_count
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,168 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.388] - 2026-10-29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Arena retain pool split by size class (leptris#1893, leptris#1846).**
|
|
8
|
+
Mid-sized spans (256 KB - 2 MB) could evict all four large-span retain
|
|
9
|
+
slots, so mixed-traffic workloads lost their warm large spans and
|
|
10
|
+
re-faulted them every parse. The retain pool is now two tiers (mid /
|
|
11
|
+
large at the 2 MB boundary) under the same shared ceiling, and a
|
|
12
|
+
pointer-identity test pins same-size reuse across mixed traffic.
|
|
13
|
+
|
|
14
|
+
### Performance
|
|
15
|
+
|
|
16
|
+
- **One-pass text-run scan (leptris#1895, leptris#1842).** The direct
|
|
17
|
+
parser's text path found `<` with a byte-at-a-time inline loop over
|
|
18
|
+
the first 48 bytes, then paid up to three libc memchr calls per run
|
|
19
|
+
(`<`, `&`, CR) — about 90ns of scan per ~55-byte run, capping
|
|
20
|
+
text-heavy documents near 800 MB/s while pugixml ran one vectorized
|
|
21
|
+
pass (profile: 72% of parse samples in the inline loop, 9% in the
|
|
22
|
+
three memchr sites). The new `leptris_text_scan_run` kernel (scalar +
|
|
23
|
+
NEON + AVX2, dispatched like the other simd_text primitives) finds
|
|
24
|
+
the first `<` and flags `&`/CR over the prefix in a single memory
|
|
25
|
+
pass; the recover-mode unescaped-`<` re-scan uses the same kernel.
|
|
26
|
+
Text shapes: 764-804 → 1370-1448 MB/s; gap vs pugixml 2.9x → 1.70x
|
|
27
|
+
(1.30x at 5 MB). Attr shapes unchanged.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## [1.9.387] - 2026-10-28
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **XSD: schema-set compilation — `leptris_xsd_compile_set(paths, count,
|
|
36
|
+
status)` (leptris#1881).** A head document cannot see
|
|
37
|
+
substitution-group members declared in schemas that IMPORT it —
|
|
38
|
+
nothing loads those from the head alone, so the head's content model
|
|
39
|
+
could never match the member. The set face merges every listed
|
|
40
|
+
document into ONE schema: per-document checks and capture with
|
|
41
|
+
per-unit target namespaces, a shared visited-set so each unit
|
|
42
|
+
captures exactly once in either member order, and extension bases
|
|
43
|
+
resolving once after all units are in. The deferred extension-base
|
|
44
|
+
fold now records the base QName's RESOLVED namespace
|
|
45
|
+
(`ext_base_ns`, prefix resolved against the extension element's
|
|
46
|
+
in-scope declarations) — set compilation extends ACROSS units
|
|
47
|
+
(g:foo1 extends f:foo), which the old same-ns fallback silently
|
|
48
|
+
dropped into an empty model.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **XSD: `processContents="skip"` wildcards impose no constraint on
|
|
53
|
+
matched children (leptris#1879).** XSD 1.0 §3.9.4.2 — the engine
|
|
54
|
+
found the child's global declaration and validated its lexical type
|
|
55
|
+
anyway (`<a>1</a>` under a skip wildcard rejected `1` as a bad
|
|
56
|
+
xs:ID; the W3C suite addB130/addB140 expect it valid).
|
|
57
|
+
`XsdCm` records the processContents level (strict/lax/skip) at both
|
|
58
|
+
wildcard capture sites; validate_children computes the strongest
|
|
59
|
+
level among the wildcards admitting each child (the attribute
|
|
60
|
+
wildcard's namespace-grammar matcher, unit-tns aware) and skips
|
|
61
|
+
skip-admitted children entirely. Lax keeps validating declared
|
|
62
|
+
children; wildcard-admitted undeclared names pass under every
|
|
63
|
+
level (the pinned moxml#460 notQName semantics).
|
|
64
|
+
|
|
65
|
+
- **XSD: attributeGroup refs resolve regardless of declaration order
|
|
66
|
+
(leptris#1880).** Top-level declarations form one symbol space, but
|
|
67
|
+
the ref expansion looked up `s->attr_groups` at capture time — a
|
|
68
|
+
group declared AFTER the referencing type was silently dropped
|
|
69
|
+
(W3C suite addB096). Top-level `xs:attributeGroup` definitions are
|
|
70
|
+
now captured in a pre-pass at the head of `xsd_capture_root`;
|
|
71
|
+
the main walk skips already-captured names instead of shadowing.
|
|
72
|
+
|
|
73
|
+
- **XSD: length facets measure the VALUE SPACE (leptris#1882).** On
|
|
74
|
+
list types the length family counts whitespace-separated ITEMS, not
|
|
75
|
+
characters (`xs:IDREFS` + `length=2` accepts `a b`, was rejected as
|
|
76
|
+
3 characters; the minLength/maxLength NMTOKENS rows flip likewise).
|
|
77
|
+
The list detection walks the base chain — a restriction OF a list
|
|
78
|
+
keeps `deriv=RESTRICT`, so `xsd_chain_is_list` resolves through
|
|
79
|
+
local bases and threads a flag into the facet gate. And
|
|
80
|
+
`xs:QName`/`xs:NOTATION` have no length family at all (Part 2
|
|
81
|
+
§4.3.1–4.3.3 applicability): the gate skips those comparisons
|
|
82
|
+
entirely.
|
|
83
|
+
|
|
84
|
+
- **XSD: the builtin lexical table converges on Datatypes Part 2
|
|
85
|
+
(leptris#1883)** — thirteen rows disagreed in both directions.
|
|
86
|
+
Over-strict, now valid: `xs:token` accepts any whitespace shape
|
|
87
|
+
(collapse is total); `xs:ID` collapses before the NCName check; the
|
|
88
|
+
unbounded integer family accepts arbitrary precision with the sign
|
|
89
|
+
class intact; `xs:float`/`xs:double` accept `+INF` (float018).
|
|
90
|
+
Too lenient, now invalid: `xs:long` rejects 2^63 (strtoll ERANGE)
|
|
91
|
+
and `xs:unsignedLong` string-compares against 2^64−1; `xs:date`
|
|
92
|
+
applies calendar-aware day bounds with leap years; `xs:time` bounds
|
|
93
|
+
hour 0–23 (`24:00:00` exact), minute 0–59, second 0–60 with 60
|
|
94
|
+
reserved for `23:59:60`; `xs:duration` rejects a bare `T` (P1Y2MT)
|
|
95
|
+
and fractions on anything but seconds (P200.5Y); `xs:base64Binary`
|
|
96
|
+
rejects a one-byte remainder (`abcde`) and data after padding;
|
|
97
|
+
`xs:QName` rejects the reserved `xmlns` prefix.
|
|
98
|
+
|
|
99
|
+
- **DOM: bulk namespace-declaration snapshot — one crossing per
|
|
100
|
+
subtree (leptris#1874).**
|
|
101
|
+
`leptris_element_namespace_declarations_snapshot(elem, ...)` walks
|
|
102
|
+
the subtree in pre-order once and returns one row per (element
|
|
103
|
+
carrying declarations, declaration) with path depth, ancestor name
|
|
104
|
+
chain, and NUL-terminated prefix/URI lanes — the same rows the
|
|
105
|
+
binding's collect_native_namespaces produces today with one
|
|
106
|
+
`namespace_definitions` FFI round trip per node plus a
|
|
107
|
+
whole-subtree Ruby recursion. attributes_snapshot retry discipline;
|
|
108
|
+
default-ns and xmlns="" undeclaration markers included.
|
|
109
|
+
|
|
110
|
+
- **DOM: address-Integer batch faces (leptris#1855).**
|
|
111
|
+
`leptris_element_children_batch(elem_addr, ...)` returns every
|
|
112
|
+
child's address + node kind in document order from ONE crossing per
|
|
113
|
+
parent (the binding's plan-path hydration measured ~35% FFI
|
|
114
|
+
materialization; `bulk_children` was 12.4% of self-time) — NODE-level
|
|
115
|
+
walk, all five child kinds ride the batch.
|
|
116
|
+
`leptris_node_name_compare(node_addr, name, len)` compares names
|
|
117
|
+
inside the engine with strcmp semantics — the `fast_name` band (4.5%)
|
|
118
|
+
was Ruby constructing Strings that mostly feed `==`.
|
|
119
|
+
|
|
120
|
+
- **XSD: test pin — a type-less element declaration defaults to
|
|
121
|
+
xs:anyType (leptris#1878).** XSD 1.0 §3.3.2: any attributes, lax
|
|
122
|
+
content, never an undeclared-element error. The moxml 0.5.153 repro
|
|
123
|
+
fails on the bundled engine 1.9.381.0 and passes since e5762cdc;
|
|
124
|
+
the moxml Gemfile already allows `~> 1.9.351` (a `bundle update
|
|
125
|
+
leptris` picks the fixed engine).
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## [1.9.386] - 2026-10-28
|
|
130
|
+
|
|
131
|
+
### Fixed
|
|
132
|
+
|
|
133
|
+
- **DOM: cross-document subtree adoption no longer corrupts attribute
|
|
134
|
+
chains when the destination arena spills into extension blocks
|
|
135
|
+
(leptris#1866).** `copy_subtree_detached` wired the first-attribute
|
|
136
|
+
edge with a truncating `int32_t` cast; once the sized arena was
|
|
137
|
+
exhausted, copy allocations landed in tracked malloc blocks that on
|
|
138
|
+
64-bit hosts sit farther from the arena span than int32 can express
|
|
139
|
+
(~105 TB on macOS). The truncated delta decoded to a wild pointer —
|
|
140
|
+
blank attribute names (`<use ="">`) and a serializer SEGV in
|
|
141
|
+
mutation-heavy SVG-rewrite loops. The edge now goes through
|
|
142
|
+
`leptris_elem_set_first_attribute`, which spills out-of-range deltas
|
|
143
|
+
to the compact overflow table, and `leptris_pool_contains`
|
|
144
|
+
recognizes extension blocks as pool-owned.
|
|
145
|
+
|
|
146
|
+
- **XSD: a local element particle with its own type shadows a
|
|
147
|
+
same-named global element declaration (leptris#1871).** Per Element
|
|
148
|
+
Locally Valid (Complex Type), XSD 1.0 §3.4.4, the matched
|
|
149
|
+
particle's declaration governs its instance element — an inline
|
|
150
|
+
anonymous (or named `type=`) local type was previously assessed as
|
|
151
|
+
the global declaration's type, rejecting valid instances and
|
|
152
|
+
accepting invalid ones. Resolution order is particle-type-first
|
|
153
|
+
(with `xsi:type` substitution applied there); `ref` particles,
|
|
154
|
+
which carry no type, keep the global lookup unchanged.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
## [1.9.385] - 2026-10-27
|
|
159
|
+
|
|
160
|
+
### Fixed
|
|
161
|
+
|
|
162
|
+
- the cross-doc adopt keeps its attribute chain; @id/.. names the owner
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
3
166
|
## [1.9.384] - 2026-10-27
|
|
4
167
|
|
|
5
168
|
### Fixed
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# arena_ab.sh — interleaved A/B for parse-arena sizing changes
|
|
3
|
+
# (leptris#1846 discipline: same-process-pair, order-alternating,
|
|
4
|
+
# median of per-pair ratios — cross-wave deltas are NOT a valid
|
|
5
|
+
# referee, the 2026-10-25 wave swung both directions at noise
|
|
6
|
+
# magnitude).
|
|
7
|
+
#
|
|
8
|
+
# Usage: arena_ab.sh <base-build-dir> <patch-build-dir> <fixture> [pairs]
|
|
9
|
+
# Both build dirs must contain benchmarks/parse_ab (the file-mode
|
|
10
|
+
# bench_parse_ab). Each pair runs BASE then PATCH, then PATCH then
|
|
11
|
+
# BASE (ABBA), so thermal/load drift cancels; the reported number is
|
|
12
|
+
# the median of patch/base MB/s ratios.
|
|
13
|
+
set -e
|
|
14
|
+
BASE=$(ls "$1"/benchmarks/parse_ab "$1"/benchmarks/bench_parse_ab 2>/dev/null | head -1)
|
|
15
|
+
PATCH=$(ls "$2"/benchmarks/parse_ab "$2"/benchmarks/bench_parse_ab 2>/dev/null | head -1)
|
|
16
|
+
FIX="$3"
|
|
17
|
+
N="${4:-10}"
|
|
18
|
+
if [ -z "$BASE" ] || [ -z "$PATCH" ] || [ -z "$FIX" ]; then
|
|
19
|
+
echo "usage: $0 <base-build-dir> <patch-build-dir> <fixture> [pairs]" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
run_one() {
|
|
23
|
+
"$1" "$FIX" | awk '{print $4}'
|
|
24
|
+
}
|
|
25
|
+
ratios=""
|
|
26
|
+
i=0
|
|
27
|
+
while [ "$i" -lt "$N" ]; do
|
|
28
|
+
a=$(run_one "$BASE"); b=$(run_one "$PATCH")
|
|
29
|
+
b2=$(run_one "$PATCH"); a2=$(run_one "$BASE")
|
|
30
|
+
ratios="$ratios
|
|
31
|
+
$(echo "$b $a" | awk '{printf "%.4f", $1/$2}')
|
|
32
|
+
$(echo "$b2 $a2" | awk '{printf "%.4f", $1/$2}')"
|
|
33
|
+
i=$((i + 1))
|
|
34
|
+
done
|
|
35
|
+
echo "$ratios" | sort -n | awk '
|
|
36
|
+
{ v[NR] = $1 }
|
|
37
|
+
END {
|
|
38
|
+
m = (NR % 2) ? v[(NR + 1) / 2] : (v[NR / 2] + v[NR / 2 + 1]) / 2
|
|
39
|
+
printf "pairs=%d median patch/base = %.4fx\n", NR, m
|
|
40
|
+
}'
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
* runners; the interleaved lane's slices are gated on THESE
|
|
6
6
|
* numbers, never a loaded dev machine (2026-09-20: a Mac at load
|
|
7
7
|
* 340 measured a 472 MB/s build at 46-85).
|
|
8
|
+
*
|
|
9
|
+
* leptris#1846: `argv[1]` switches to file mode — parse+free one
|
|
10
|
+
* fixture in a loop (median of N in-process runs, best-of-5
|
|
11
|
+
* batches) and print one `FILE <path> <bytes> <MB/s>` row. The
|
|
12
|
+
* arena-sizing A/B runner (arena_ab.sh) interleaves two builds of
|
|
13
|
+
* this binary and takes the median of per-pair ratios, so runner
|
|
14
|
+
* load cannot fake or hide the effect.
|
|
8
15
|
*/
|
|
9
16
|
#include <stdio.h>
|
|
10
17
|
#include <stdlib.h>
|
|
@@ -22,6 +29,45 @@ static double now_us(void) {
|
|
|
22
29
|
return ts.tv_sec * 1e6 + ts.tv_nsec / 1e3;
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
static int file_mode(const char* path) {
|
|
33
|
+
FILE* f = fopen(path, "rb");
|
|
34
|
+
if (!f) { fprintf(stderr, "cannot open %s\n", path); return 1; }
|
|
35
|
+
fseek(f, 0, SEEK_END);
|
|
36
|
+
long sz = ftell(f);
|
|
37
|
+
fseek(f, 0, SEEK_SET);
|
|
38
|
+
char* doc = (char*)malloc((size_t)sz);
|
|
39
|
+
if (!doc || fread(doc, 1, (size_t)sz, f) != (size_t)sz) {
|
|
40
|
+
fprintf(stderr, "cannot read %s\n", path);
|
|
41
|
+
fclose(f);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
fclose(f);
|
|
45
|
+
/* LEPTRIS_BENCH_ITERS: profiling escape hatch (sample(1) needs a
|
|
46
|
+
* long-running process, not a 10ms one). */
|
|
47
|
+
const char* iters_env = getenv("LEPTRIS_BENCH_ITERS");
|
|
48
|
+
int iters = iters_env ? atoi(iters_env) : 5;
|
|
49
|
+
double best = 1e18;
|
|
50
|
+
double sum = 0;
|
|
51
|
+
for (int r = 0; r < iters; r++) {
|
|
52
|
+
double a = now_us();
|
|
53
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
54
|
+
LeptrisDocument d = leptris_parse_string(doc, (size_t)sz, &st);
|
|
55
|
+
double b = now_us();
|
|
56
|
+
if (!d) { printf("PARSE FAILED\n"); return 1; }
|
|
57
|
+
leptris_document_free(d);
|
|
58
|
+
if (b - a < best) best = b - a;
|
|
59
|
+
sum += b - a;
|
|
60
|
+
}
|
|
61
|
+
/* #1846: the MEAN prices in the cold parses (fresh span + full
|
|
62
|
+
* first-touch) that a best-of hides — the warm-span retention fix
|
|
63
|
+
* lives exactly there. */
|
|
64
|
+
printf("FILE %s %ld %.1f MB/s mean %.1f MB/s\n", path, sz,
|
|
65
|
+
sz / 1048576.0 / (best / 1e6),
|
|
66
|
+
sz / 1048576.0 / ((sum / (double)iters) / 1e6));
|
|
67
|
+
free(doc);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
25
71
|
static size_t gen(char* out, size_t target, int text_shape) {
|
|
26
72
|
size_t len = 0;
|
|
27
73
|
len += (size_t)sprintf(out + len, "<root>");
|
|
@@ -40,7 +86,8 @@ static size_t gen(char* out, size_t target, int text_shape) {
|
|
|
40
86
|
return len;
|
|
41
87
|
}
|
|
42
88
|
|
|
43
|
-
int main(
|
|
89
|
+
int main(int argc, char** argv) {
|
|
90
|
+
if (argc > 1) return file_mode(argv[1]);
|
|
44
91
|
const size_t sizes[3] = {1500 * 1024, 5000 * 1024, 13000 * 1024};
|
|
45
92
|
const char* names[2] = {"attr", "text"};
|
|
46
93
|
printf("%-6s %9s %12s %12s\n", "shape", "bytes", "leptris", "pugixml");
|
|
@@ -293,6 +293,22 @@ LEPTRIS_API LeptrisPlanResult leptris_plan_result_build(
|
|
|
293
293
|
LeptrisPlan plan, const leptris_plan_row_op* ops, size_t count,
|
|
294
294
|
LeptrisStatus* status);
|
|
295
295
|
|
|
296
|
+
/* leptris#1854: build a result from a PACKED op blob — the whole
|
|
297
|
+
* emission crosses in one call and no per-op host struct writes
|
|
298
|
+
* (the #408 address-Integer law applied to the build side; the
|
|
299
|
+
* binding's emission loop was 37% FFI::Pointer#+). Same program
|
|
300
|
+
* semantics as leptris_plan_result_build. Blob layout, per op:
|
|
301
|
+
* five native-byte-order uint32_t (kind, plan_index, row_index,
|
|
302
|
+
* name_len, value_len), then name_len name bytes PLUS a NUL
|
|
303
|
+
* terminator (the ATTR path dup's names NUL-terminated), then
|
|
304
|
+
* value_len raw value bytes; a zero length omits the bytes and
|
|
305
|
+
* reads as NULL. Bounds-checked: a truncated blob fails
|
|
306
|
+
* INVALID_ARG. Strings are copied; the blob is only read during
|
|
307
|
+
* the call. Memory: free with leptris_plan_result_free. */
|
|
308
|
+
LEPTRIS_API LeptrisPlanResult leptris_plan_result_build_packed(
|
|
309
|
+
LeptrisPlan plan, const void* blob, size_t blob_len,
|
|
310
|
+
LeptrisStatus* status);
|
|
311
|
+
|
|
296
312
|
/* #1551: serialize a walk result back to XML, guided by its plan.
|
|
297
313
|
* The plan supplies element wrappers (row wire_name + ns_prefix)
|
|
298
314
|
* and the result supplies content; children emit in document order
|
|
@@ -913,6 +913,21 @@ LEPTRIS_API void leptris_rng_free(LeptrisRelaxNG rng);
|
|
|
913
913
|
LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile_file(const char* path,
|
|
914
914
|
LeptrisStatus* status);
|
|
915
915
|
|
|
916
|
+
/* leptris#1881: compile a schema SET — every listed document's
|
|
917
|
+
* declarations merge into ONE schema, in order, with the shared
|
|
918
|
+
* visited-set closing include/import cycles across members. A head
|
|
919
|
+
* document cannot see substitution-group members declared in
|
|
920
|
+
* schemas that import it (nothing loads those from the head alone);
|
|
921
|
+
* a set compilation does. Per-unit target namespaces are honored
|
|
922
|
+
* during each document's capture; the FIRST document's
|
|
923
|
+
* targetNamespace remains the schema's. Relative schemaLocations
|
|
924
|
+
* resolve against each document's own directory. NULL paths or
|
|
925
|
+
* count 0 return NULL; an unreadable or malformed member yields an
|
|
926
|
+
* error-carrying handle like leptris_xsd_compile_file.
|
|
927
|
+
* (Memory: free with leptris_xsd_free.) */
|
|
928
|
+
LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile_set(
|
|
929
|
+
const char* const* paths, size_t count, LeptrisStatus* status);
|
|
930
|
+
|
|
916
931
|
LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile(const char* xsd_text,
|
|
917
932
|
size_t len,
|
|
918
933
|
LeptrisStatus* status);
|
|
@@ -2019,6 +2034,68 @@ LEPTRIS_API size_t leptris_element_attributes_snapshot(
|
|
|
2019
2034
|
char* ns_prefixes, size_t prefixes_cap, size_t* prefix_offsets,
|
|
2020
2035
|
char* ns_uris, size_t uris_cap, size_t* uri_offsets);
|
|
2021
2036
|
|
|
2037
|
+
/* leptris#1855 (ask 1): batched child materialization under the
|
|
2038
|
+
* #408 address-Integer law — one crossing per PARENT returns every
|
|
2039
|
+
* child's address and node kind, replacing the binding's per-child
|
|
2040
|
+
* FFI walk. Returns the element's child count; the (addr, kind)
|
|
2041
|
+
* pairs are written in document order only when cap >= count —
|
|
2042
|
+
* otherwise nothing is written and the count comes back so the
|
|
2043
|
+
* caller sizes and retries. A 0 address reads as "no children". */
|
|
2044
|
+
LEPTRIS_API size_t leptris_element_children_batch(
|
|
2045
|
+
uintptr_t elem_addr,
|
|
2046
|
+
uintptr_t* child_addrs, uint8_t* child_kinds, size_t cap);
|
|
2047
|
+
|
|
2048
|
+
/* leptris#1855 (ask 2): engine-side name comparison — strcmp
|
|
2049
|
+
* semantics (-1/0/1) between the node's name and the caller's
|
|
2050
|
+
* (name, len) lens, with no String constructed on either side.
|
|
2051
|
+
* Element nodes compare their name; non-element nodes compare as
|
|
2052
|
+
* the empty string. A 0 address is an error lane: -2. */
|
|
2053
|
+
LEPTRIS_API int leptris_node_name_compare(uintptr_t node_addr,
|
|
2054
|
+
const char* name, size_t len);
|
|
2055
|
+
|
|
2056
|
+
/* leptris#1874: bulk namespace-declaration snapshot — the whole
|
|
2057
|
+
* subtree's declared namespaces in ONE crossing (the binding's
|
|
2058
|
+
* collect_native_namespaces walk paid one namespace_definitions FFI
|
|
2059
|
+
* round trip per node). Pre-order DFS under `elem`; one row per
|
|
2060
|
+
* (element carrying declarations, declaration) in document order,
|
|
2061
|
+
* exactly the rows the binding's per-node namespace_definitions
|
|
2062
|
+
* union produces.
|
|
2063
|
+
*
|
|
2064
|
+
* Each row carries the element's path depth (0 = `elem` itself, 1 =
|
|
2065
|
+
* a child, ...) and blob offsets: the path names (depth NUL-
|
|
2066
|
+
* terminated components in ancestor-first order, `names_off`), the
|
|
2067
|
+
* prefix (`prefix_off`/`prefix_len`, NUL-terminated in the blob;
|
|
2068
|
+
* 0xFFFFFFFFu off = a default-namespace declaration, no prefix),
|
|
2069
|
+
* and the URI (`href_off`/`href_len`, NUL-terminated in the blob;
|
|
2070
|
+
* len 0 = an xmlns="" undeclaration). Legacy xmlns:* attributes
|
|
2071
|
+
* added through the attribute API are included (same union as
|
|
2072
|
+
* leptris_element_namespace_count).
|
|
2073
|
+
*
|
|
2074
|
+
* Contract (the attributes_snapshot discipline): with `rows == NULL`
|
|
2075
|
+
* returns 0 and writes the required row count to *out_row_count and
|
|
2076
|
+
* the required blob bytes to *blob_used (either pointer may be
|
|
2077
|
+
* NULL); with non-NULL buffers smaller than required returns
|
|
2078
|
+
* (size_t)-1 and writes nothing; on success returns 0 and writes
|
|
2079
|
+
* the used blob bytes to *blob_used when non-NULL.
|
|
2080
|
+
*
|
|
2081
|
+
* Memory: the snapshot copies strings into the caller's blob;
|
|
2082
|
+
* nothing borrows the document.
|
|
2083
|
+
*/
|
|
2084
|
+
typedef struct {
|
|
2085
|
+
uint32_t depth; /* 0 = elem itself, 1 = child, ... */
|
|
2086
|
+
uint32_t name_count; /* == depth */
|
|
2087
|
+
uint32_t names_off; /* blob offset of the first path name */
|
|
2088
|
+
uint32_t prefix_off; /* 0xFFFFFFFFu = default-ns declaration */
|
|
2089
|
+
uint32_t prefix_len;
|
|
2090
|
+
uint32_t href_off;
|
|
2091
|
+
uint32_t href_len;
|
|
2092
|
+
} leptris_ns_decl_row;
|
|
2093
|
+
|
|
2094
|
+
LEPTRIS_API size_t leptris_element_namespace_declarations_snapshot(
|
|
2095
|
+
LeptrisElement elem,
|
|
2096
|
+
leptris_ns_decl_row* rows, size_t rows_cap, size_t* out_row_count,
|
|
2097
|
+
char* blob, size_t blob_cap, size_t* blob_used);
|
|
2098
|
+
|
|
2022
2099
|
/**
|
|
2023
2100
|
* Get name of attribute by index
|
|
2024
2101
|
*
|