leptris 1.9.107.0 → 1.9.115.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/Rakefile +1 -1
- data/TODO.restructure/22-sax-drain-bulk-strip.md +28 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +13 -0
- data/lib/leptris/xml/relaxng.rb +75 -0
- data/lib/leptris/xml/sax/recorder.rb +11 -7
- data/lib/leptris/xml.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8fb86a086496ee90110e37c8049ccb26b472d7a21f0f774a381e7611deb1999a
|
|
4
|
+
data.tar.gz: 1ec17ae04d09a31ef6e38ead74151be098373d1e0863b8e59755c2ebdb01cc3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4733922a9794ada70fba4b50783b87a4748a04145131bba696eeb1b2ab3ae33d8f76fbcabb531f8fbc93be51f3a53634841f3488f42c238a5d93b22bdd732a75
|
|
7
|
+
data.tar.gz: e1ffd73a63379edd8c23018ab1477f0627e783a85c52ec8210b82e7efcad16caf063a01bddf661e2b05cae26315bb800484c2560f5841b47d093432d40db9ed7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ 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.115.0] - 2026-09-09
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Lockstep with libleptris 1.9.115: the RELAX NG arc completes —
|
|
13
|
+
the validator (Jing conformance gate 38/38), `<include>` with
|
|
14
|
+
Jing's override rules, `<param>` datatype facets (portable
|
|
15
|
+
matcher), and schema errors on `leptris_last_error`. Required by
|
|
16
|
+
the RelaxNG binding (#165).
|
|
17
|
+
|
|
18
|
+
## [1.9.107.1] - 2026-09-08
|
|
19
|
+
|
|
20
|
+
### Improved
|
|
21
|
+
|
|
22
|
+
- **SAX drain: bulk kind strip** (TODO.restructure/22 — the moxml
|
|
23
|
+
0.77-0.93x row): the recorder's dispatch loop read the event
|
|
24
|
+
kind with a `get_uint8` per EVENT (a memory crossing for every
|
|
25
|
+
record, dispatched or not); the strip now reads in ONE bulk
|
|
26
|
+
`get_bytes` + ONE `unpack` per drain via the memoized
|
|
27
|
+
per-count template — kind codes 0..10 unpack to immediate
|
|
28
|
+
Fixnums, so the change strictly removes ~count FFI crossings
|
|
29
|
+
with zero new allocations. Allocation profile (load-independent
|
|
30
|
+
measurement) is far ahead either way: text-only 32,381 vs
|
|
31
|
+
Nokogiri's 400,061 allocs/parse (12x fewer); all-events 112,440
|
|
32
|
+
vs 400,034 (3.6x fewer) — the remaining gap is pure per-event
|
|
33
|
+
CPU, which this addresses. Timing verification gated on a clean
|
|
34
|
+
host window (bench staged; the shared machine sat at load
|
|
35
|
+
40-400 today).
|
|
36
|
+
|
|
8
37
|
## [1.9.107.0] - 2026-09-08
|
|
9
38
|
|
|
10
39
|
### Changed
|
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.115"
|
|
12
12
|
|
|
13
13
|
CMAKE_FLAGS = %w[
|
|
14
14
|
-DCMAKE_BUILD_TYPE=Release
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 22 — SAX drain: bulk kind strip (the 0.77-0.93x row)
|
|
2
|
+
|
|
3
|
+
Status: CODE DONE — timing verification gated on a clean window
|
|
4
|
+
|
|
5
|
+
The moxml table shows SAX at 0.77-0.93x Nokogiri (it fully won at
|
|
6
|
+
the 1.9.40-era battery). Investigation under load-404 conditions
|
|
7
|
+
(timing unusable; allocation profiling is load-independent):
|
|
8
|
+
|
|
9
|
+
- **Allocations are NOT the gap** — measured GC-stat deltas:
|
|
10
|
+
text-only 32,381 vs Nokogiri's 400,061 allocs/parse (12x fewer);
|
|
11
|
+
all-events 112,440 vs 400,034 (3.6x fewer).
|
|
12
|
+
- **The time cost was per-event FFI crossings in the drain**: the
|
|
13
|
+
kind strip was read with a `get_uint8` per EVENT (all events,
|
|
14
|
+
dispatched or not — a memory crossing each) after the
|
|
15
|
+
whole-record-unpack was retired for its 2.25M-Integer profile.
|
|
16
|
+
- **Fix**: the kind strip reads in ONE `get_bytes` + ONE `unpack`
|
|
17
|
+
per drain through the already-defined memoized
|
|
18
|
+
`KIND_TEMPLATE_FOR` — kind codes 0..10 unpack to IMMEDIATE
|
|
19
|
+
Fixnums (zero allocation), so this strictly removes ~count FFI
|
|
20
|
+
crossings per drain with no new allocations. Per-dispatched-event
|
|
21
|
+
field reads (2-3 `get_uint32`) remain.
|
|
22
|
+
|
|
23
|
+
- [x] Bulk strip restored; SAX suite green; allocation profile
|
|
24
|
+
re-verified (32k/112k vs 400k/400k).
|
|
25
|
+
- [ ] **Timing verification** (the actual 0.77-0.93x row): bench
|
|
26
|
+
staged at /tmp/bench_sax.rb — requires load < 20 (the host
|
|
27
|
+
has been at 40-400). Gate: text-only and all-events SAX at
|
|
28
|
+
parity or better vs Nokogiri on the raw binding.
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -42,6 +42,7 @@ module Leptris
|
|
|
42
42
|
typedef :pointer, :leptris_xpath_result
|
|
43
43
|
typedef :pointer, :leptris_xpath_ns_set
|
|
44
44
|
typedef :pointer, :leptris_xpath_compiled
|
|
45
|
+
typedef :pointer, :leptris_relaxng
|
|
45
46
|
typedef :pointer, :leptris_xslt
|
|
46
47
|
typedef :pointer, :leptris_xquery
|
|
47
48
|
typedef :pointer, :leptris_sax_recorder
|
|
@@ -733,6 +734,18 @@ attach_function :leptris_parse_string,
|
|
|
733
734
|
attach_function :leptris_str_has_nonstandard_entity,
|
|
734
735
|
[:string, :size_t], :int
|
|
735
736
|
|
|
737
|
+
attach_function :leptris_rng_parse,
|
|
738
|
+
[:string, :size_t, :pointer], :leptris_relaxng
|
|
739
|
+
attach_function :leptris_rng_parse_file,
|
|
740
|
+
[:string, :pointer], :leptris_relaxng
|
|
741
|
+
attach_function :leptris_rng_free,
|
|
742
|
+
[:leptris_relaxng], :void
|
|
743
|
+
# 1 valid; 0 invalid (detail via leptris_rng_error)
|
|
744
|
+
attach_function :leptris_rng_validate,
|
|
745
|
+
[:leptris_relaxng, :leptris_document], :int
|
|
746
|
+
attach_function :leptris_rng_error,
|
|
747
|
+
[:leptris_relaxng], :string
|
|
748
|
+
|
|
736
749
|
attach_function :leptris_xslt_parse,
|
|
737
750
|
[:string, :size_t], :leptris_xslt
|
|
738
751
|
attach_function :leptris_xslt_parse_file,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Leptris::XML::RelaxNG
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def parse(schema_xml)
|
|
9
|
+
Schema.parse(schema_xml)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse_file(path)
|
|
13
|
+
Schema.parse_file(path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# A compiled RELAX NG schema (libleptris >= 1.9.115): Jing-parity
|
|
17
|
+
# validation against the standard DOM — parse once, validate any
|
|
18
|
+
# number of documents. Schema errors publish to the thread-global
|
|
19
|
+
# error channel, so failures raise with the engine's detail
|
|
20
|
+
# ("unknown pattern element: ...", "cannot open schema file ...").
|
|
21
|
+
#
|
|
22
|
+
# rng = Leptris::XML::RelaxNG.parse(schema_xml)
|
|
23
|
+
# rng.valid?(doc) # => true/false
|
|
24
|
+
# rng.validate(doc) # => [] or ["1:0: error: element ..."]
|
|
25
|
+
#
|
|
26
|
+
class Schema
|
|
27
|
+
# GC-managed compiled handle.
|
|
28
|
+
class Handle < ::FFI::AutoPointer
|
|
29
|
+
def self.release(ptr)
|
|
30
|
+
Leptris::XML::FFI.leptris_rng_free(ptr)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Compile a schema from XML text. Raises Error on a schema
|
|
35
|
+
# error (detail via leptris_last_error).
|
|
36
|
+
def self.parse(schema_xml)
|
|
37
|
+
xml = schema_xml.to_s
|
|
38
|
+
raw = Leptris::XML::FFI.leptris_rng_parse(xml, xml.bytesize, nil)
|
|
39
|
+
if raw.null?
|
|
40
|
+
raise Leptris::XML::Error,
|
|
41
|
+
"schema parse failed: #{Leptris::XML::FFI.leptris_last_error}"
|
|
42
|
+
end
|
|
43
|
+
new(Handle.new(raw))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Compile from a file; <include href="..."> resolves relative to
|
|
47
|
+
# the file's directory (Jing's override rules apply).
|
|
48
|
+
def self.parse_file(path)
|
|
49
|
+
raw = Leptris::XML::FFI.leptris_rng_parse_file(path.to_s, nil)
|
|
50
|
+
if raw.null?
|
|
51
|
+
raise Leptris::XML::Error,
|
|
52
|
+
"schema parse failed: #{Leptris::XML::FFI.leptris_last_error}"
|
|
53
|
+
end
|
|
54
|
+
new(Handle.new(raw))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(handle)
|
|
58
|
+
@handle = handle
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Nokogiri shape: an array of error strings (the engine emits
|
|
62
|
+
# Jing's "line:col: error: message" form), empty when valid.
|
|
63
|
+
def validate(document)
|
|
64
|
+
ok = Leptris::XML::FFI.leptris_rng_validate(@handle,
|
|
65
|
+
document.c_ptr)
|
|
66
|
+
return [] if ok != 0
|
|
67
|
+
[Leptris::XML::FFI.leptris_rng_error(@handle)].compact
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def valid?(document)
|
|
71
|
+
Leptris::XML::FFI.leptris_rng_validate(@handle,
|
|
72
|
+
document.c_ptr) != 0
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -159,16 +159,20 @@ class Leptris::XML::SAX::Recorder
|
|
|
159
159
|
@handle, len_ptr)
|
|
160
160
|
arena = arena_ptr.read_bytes(len_ptr.read_uint64)
|
|
161
161
|
count = count_ptr.read_uint64
|
|
162
|
-
# Kind
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
162
|
+
# Kind strip in ONE bulk read + ONE unpack per drain: the
|
|
163
|
+
# per-count template reads the first byte of each record,
|
|
164
|
+
# skipping the stride (KIND_TEMPLATE_FOR is memoized per
|
|
165
|
+
# count). Kind codes are 0..10 — unpack yields immediate
|
|
166
|
+
# Fixnums, so the strip allocates nothing. This replaces a
|
|
167
|
+
# get_uint8 crossing per EVENT (all events, dispatched or
|
|
168
|
+
# not) with one memory read per drain — the SAX gap to
|
|
169
|
+
# Nokogiri's per-event C callback lives exactly here.
|
|
170
|
+
kinds = records_ptr.get_bytes(0, count * RECORD_STRIDE)
|
|
171
|
+
.unpack(KIND_TEMPLATE_FOR[count])
|
|
167
172
|
one_arg_start = dispatched[:start_element] == :one_arg
|
|
168
173
|
i = 0
|
|
169
174
|
while i < count
|
|
170
|
-
|
|
171
|
-
kind = KIND_BY_CODE[code]
|
|
175
|
+
kind = kinds[i] && KIND_BY_CODE[kinds[i]]
|
|
172
176
|
if kind && dispatched[kind]
|
|
173
177
|
base = i * RECORD_STRIDE
|
|
174
178
|
case kind
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Leptris
|
|
|
28
28
|
autoload :ResultAttr, "leptris/xml/result_attr"
|
|
29
29
|
autoload :XSLT, "leptris/xml/xslt"
|
|
30
30
|
autoload :XQuery, "leptris/xml/xquery"
|
|
31
|
+
autoload :RelaxNG, "leptris/xml/relaxng"
|
|
31
32
|
autoload :Pull, "leptris/xml/pull"
|
|
32
33
|
autoload :Iterparse, "leptris/xml/iterparse"
|
|
33
34
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.115.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -90,6 +90,7 @@ files:
|
|
|
90
90
|
- TODO.restructure/19-gitignore-hygiene.md
|
|
91
91
|
- TODO.restructure/20-utf8proc-enablement.md
|
|
92
92
|
- TODO.restructure/21-typeddata-variant-prototype.md
|
|
93
|
+
- TODO.restructure/22-sax-drain-bulk-strip.md
|
|
93
94
|
- docs/adr/0001-lockstep-mirror.md
|
|
94
95
|
- docs/adr/0002-utf8-at-the-seam.md
|
|
95
96
|
- docs/adr/0003-readonly-memoization-pattern.md
|
|
@@ -119,6 +120,7 @@ files:
|
|
|
119
120
|
- lib/leptris/xml/parse_options.rb
|
|
120
121
|
- lib/leptris/xml/processing_instruction.rb
|
|
121
122
|
- lib/leptris/xml/pull.rb
|
|
123
|
+
- lib/leptris/xml/relaxng.rb
|
|
122
124
|
- lib/leptris/xml/result_attr.rb
|
|
123
125
|
- lib/leptris/xml/result_text.rb
|
|
124
126
|
- lib/leptris/xml/sax.rb
|