leptris 1.9.174.4 → 1.9.174.6
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 +33 -0
- data/TODO.perf/25-scope-owned-bulk-path.md +39 -0
- data/TODO.perf/26-post-mutation-memo-seeding.md +26 -0
- data/TODO.perf/27-c-yield-traversal.md +38 -0
- data/TODO.perf/28-address-fills.md +25 -0
- data/ext/leptris/native/native.c +257 -2
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/cdata.rb +5 -1
- data/lib/leptris/xml/comment.rb +5 -1
- data/lib/leptris/xml/element.rb +35 -5
- data/lib/leptris/xml/node.rb +39 -6
- data/lib/leptris/xml/processing_instruction.rb +11 -3
- data/lib/leptris/xml/text.rb +5 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ac7143c5a7730e13077c52448c2b90a7705959a4fe458e7c29e7782d7ee6df1
|
|
4
|
+
data.tar.gz: 23d3530348907214d341704fa7b098728fa8b677422c8f595e12ae33c2b88ff3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6395fec9ab3701df1b77f32bbe950feff8b918b9f79374b5c107d9e282bb5e9e5fdd91c152dc46526fc201910e69b7c57e8ee968d11e222be3cfdd346c6c7ab8
|
|
7
|
+
data.tar.gz: a55e0b100034203af05855e8881fbfa161d30d6043b3831080ebff8d0f9ca658ca74fd11e1d30aef6887b3108493e4f690f0e08133ca4cf3bca36399cbcacdc4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ 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.174.6] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **C-yield traversal (TODO.perf/27)**: `traverse` and `visit`
|
|
13
|
+
no longer allocate an FFI::Function closure per call — the
|
|
14
|
+
ext's callbacks rb_yield directly, preserving post-order +
|
|
15
|
+
abort-at-self + stash-abort-raise (traverse) and the
|
|
16
|
+
(node, entering, depth) visit contract. Measured (load 34):
|
|
17
|
+
**traverse over 8k nodes 1663.5µs → 379.9µs (4.4x; 6.81x
|
|
18
|
+
Nokogiri); visit 2288.6µs → 545.4µs (4.2x)**.
|
|
19
|
+
- **Address-based first-touch fills (TODO.perf/28)**:
|
|
20
|
+
text/comment/CDATA/PI content and `path` fill through address
|
|
21
|
+
faces (scope-eligible); the bulk attribute faces accept
|
|
22
|
+
scope-owned elements.
|
|
23
|
+
|
|
24
|
+
## [1.9.174.5] - 2026-09-15
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Iterparse rides the bulk path (TODO.perf/25)**: scope-owned
|
|
29
|
+
elements materialize children through the one-pass C faces —
|
|
30
|
+
the IterationScope flows as the cache/version authority (its
|
|
31
|
+
cache resets per yield, so recycled pool addresses cannot
|
|
32
|
+
collide), with scope-aware wrapper stamps; pure address-based
|
|
33
|
+
reads (attribute/text/name) become scope-eligible. Mutations
|
|
34
|
+
on scope elements keep the Ruby path. Measured (load ~6): the
|
|
35
|
+
20k-record streaming walk 124.8ms → 100.5ms (~20%).
|
|
36
|
+
- **Post-mutation memo seeding (TODO.perf/26)**: []= seeds the
|
|
37
|
+
attribute memo at the new version (partial memos extend,
|
|
38
|
+
full faces restart — the cold-[] rules); content= seeds
|
|
39
|
+
@content; the write-then-read cycle runs ~545ns combined.
|
|
40
|
+
|
|
8
41
|
## [1.9.174.4] - 2026-09-15
|
|
9
42
|
|
|
10
43
|
### Changed
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 25 — Scope-owned (iterparse) elements on the bulk/native read path
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.5)
|
|
4
|
+
|
|
5
|
+
iterparse is moxml's streaming core, yet scope-owned elements
|
|
6
|
+
ride the FFI read path (two-call fetch + per-child Ruby wrap
|
|
7
|
+
frames; #4ca5b3c kept them off deliberately). The IterationScope
|
|
8
|
+
exposes exactly the seams the C faces read — @wrapper_cache
|
|
9
|
+
(reset per yield by new_subtree!, killing recycled-address
|
|
10
|
+
entries) and @version (advances per yield and on mutation) — so
|
|
11
|
+
the bulk children faces can take the SCOPE as their "document"
|
|
12
|
+
argument safely, provided the constructed wrappers are stamped
|
|
13
|
+
scope-aware: @structure_memoizable/@native_fast false,
|
|
14
|
+
@pub_document nil.
|
|
15
|
+
|
|
16
|
+
Pure address-based reads (fast_attribute / element text / name)
|
|
17
|
+
touch no cache — scope elements can use them via a construction
|
|
18
|
+
-time @addr_reads_fast that ignores scope ownership.
|
|
19
|
+
|
|
20
|
+
Gates: iterparse children/element_children identical via the C
|
|
21
|
+
path; identity holds within a yielded subtree and resets across
|
|
22
|
+
yields (the new_subtree! discipline); document stays nil for
|
|
23
|
+
scope yields; mutations on scope elements keep the Ruby path;
|
|
24
|
+
suite both modes.
|
|
25
|
+
|
|
26
|
+
## Outcome (1.9.174.5)
|
|
27
|
+
|
|
28
|
+
The bulk children faces take the IterationScope itself as their
|
|
29
|
+
"document" (its @wrapper_cache resets per yield via new_subtree!
|
|
30
|
+
— recycled pool addresses cannot collide — and its @version
|
|
31
|
+
advances per yield and on mutation); the C face stamps
|
|
32
|
+
scope-aware (@structure_memoizable/@native_fast false,
|
|
33
|
+
@pub_document nil) while document-owned stamps stay Qtrue.
|
|
34
|
+
Pure address-based reads (fast_attribute / element text / name)
|
|
35
|
+
switch to @addr_reads_fast — true for scope elements too (they
|
|
36
|
+
touch no cache). Mutations on scope elements keep the Ruby path.
|
|
37
|
+
Measured (load ~6): the 20k-record iterparse walk (attr + child
|
|
38
|
+
name + content per record) 124.8ms (FFI) -> 100.5ms (native
|
|
39
|
+
bulk) — ~20%.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 26 — Post-mutation memo seeding
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.5)
|
|
4
|
+
|
|
5
|
+
Every mutation invalidates the read memos through the version
|
|
6
|
+
bump, so a write-then-read cycle pays a full re-derivation even
|
|
7
|
+
when the new value is known at write time. Seed it: Element#[]=
|
|
8
|
+
re-stamps @attr_values at the new version and writes the value
|
|
9
|
+
into it (a partial fill stays partial — same rules); content=
|
|
10
|
+
stores the new text as the @content memo at the new version;
|
|
11
|
+
name= already seeds @name.
|
|
12
|
+
|
|
13
|
+
Gates: []=/content=/name= followed by reads answers from the
|
|
14
|
+
seeded memo (spec-observable via identity for strings? — no,
|
|
15
|
+
values only); mixed attribute sets then attributes() still
|
|
16
|
+
rebuild the full face on demand; invalidation semantics
|
|
17
|
+
unchanged for other mutators.
|
|
18
|
+
|
|
19
|
+
## Outcome (1.9.174.5)
|
|
20
|
+
|
|
21
|
+
Element#[]= seeds the version-stamped @attr_values at the new
|
|
22
|
+
version (a just-invalidated valid partial memo extends in place;
|
|
23
|
+
a full face or anything older restarts partial, mirroring the
|
|
24
|
+
cold-[] rules); content= seeds @content; name= seeds @name
|
|
25
|
+
(already). Measured: the []= + [] read-back cycle runs ~545ns
|
|
26
|
+
(write + read combined).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 27 — C-yield traversal (traverse/visit without the FFI::Function closure)
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.6)
|
|
4
|
+
|
|
5
|
+
Node#traverse and #visit allocate an FFI::Function closure per
|
|
6
|
+
call (~1µs) and dispatch every visited node back through the
|
|
7
|
+
Ruby closure — the dominant per-node cost of whole-subtree
|
|
8
|
+
iteration, paid even on small subtrees where the walk itself is
|
|
9
|
+
microseconds.
|
|
10
|
+
|
|
11
|
+
Bind both to C callbacks that rb_yield directly: the ext's
|
|
12
|
+
traverse face keeps the abort-at-self post-order contract and
|
|
13
|
+
the exception discipline (rb_protect in the callback stashes,
|
|
14
|
+
aborts the walk, re-raises after — exactly the Ruby version's
|
|
15
|
+
contract); visit keeps its (node, entering, depth) callback
|
|
16
|
+
shape with the same stash-and-raise treatment. Node wrapping per
|
|
17
|
+
visit rides the shared binding construction (identity cache +
|
|
18
|
+
kind dispatch), and the walk state lives on the C stack — no
|
|
19
|
+
globals, nested traversals safe.
|
|
20
|
+
|
|
21
|
+
Gates: post-order sequence identical (receiver last); the
|
|
22
|
+
subtree boundary holds (abort-at-self); block exceptions
|
|
23
|
+
re-raise after the walk stops; enum_for forms unchanged; suite
|
|
24
|
+
both modes.
|
|
25
|
+
|
|
26
|
+
## Outcome (1.9.174.6)
|
|
27
|
+
|
|
28
|
+
Native.traverse_binding / visit_binding: C callbacks that
|
|
29
|
+
rb_yield directly (block forwarded explicitly — rb_yield needs
|
|
30
|
+
it on the C entry's frame). traverse keeps post-order +
|
|
31
|
+
abort-at-self + stash-abort-raise (rb_protect in the callback,
|
|
32
|
+
walk state on the C stack — nested traversals independent);
|
|
33
|
+
visit keeps (node, entering, depth) with the engine's
|
|
34
|
+
(user, node, ...) callback order. Two bugs found by the suite:
|
|
35
|
+
the visit callback's argument order (user first — the segfault
|
|
36
|
+
spec caught it) and the block forwarding. Measured (load 34):
|
|
37
|
+
traverse over 8k nodes 1663.5µs (FFI closure) -> 379.9µs
|
|
38
|
+
(4.4x; 6.81x Nokogiri); visit 2288.6µs -> 545.4µs (4.2x).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 28 — Address-based first-touch fills for the remaining reads
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.6)
|
|
4
|
+
|
|
5
|
+
The memoized content readers still fill through FFI marshaling on
|
|
6
|
+
first touch — Text/Comment/CDATA/PI content, and Node#path
|
|
7
|
+
(owned-string read + free) — and the bulk attribute faces exclude
|
|
8
|
+
scope-owned elements though they touch no document state. Add the
|
|
9
|
+
trivial address faces (fast_text/comment/cdata content, pi
|
|
10
|
+
target/data, node path with the free protocol) and switch the
|
|
11
|
+
fills to @addr_reads_fast (scope-eligible); the attributes bulk
|
|
12
|
+
path follows.
|
|
13
|
+
|
|
14
|
+
Gates: identical values for every kind (UTF-8, escapes, nils);
|
|
15
|
+
scope-owned fills answer the same; path memo unchanged; suite
|
|
16
|
+
both modes.
|
|
17
|
+
|
|
18
|
+
## Outcome (1.9.174.6)
|
|
19
|
+
|
|
20
|
+
fast_text/comment/cdata content, fast_pi_target/data (the
|
|
21
|
+
read_pi_data strip preserved), and fast_path (owned-string copy
|
|
22
|
+
+ engine free) — every first-touch content fill now rides the
|
|
23
|
+
address faces via @addr_reads_fast (scope-eligible), and the
|
|
24
|
+
bulk attribute faces accept scope-owned elements (they touch no
|
|
25
|
+
document state; the scope's version stamps the memo).
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -87,8 +87,18 @@ static node_str_fn f_comment_content, f_cdata_content, f_pi_target,
|
|
|
87
87
|
f_pi_data;
|
|
88
88
|
typedef int (*set_str_fn)(void *, const char *);
|
|
89
89
|
typedef int (*node_unlink_fn)(void *);
|
|
90
|
+
typedef int (*traverse_cb_fn)(void *, void *);
|
|
91
|
+
typedef int (*traverse_fn)(void *, int, traverse_cb_fn, void *);
|
|
92
|
+
typedef void (*visit_cb_fn)(void *, void *, int, int);
|
|
93
|
+
typedef void (*visit_fn)(void *, visit_cb_fn, void *);
|
|
94
|
+
typedef void (*free_str_fn)(void *);
|
|
95
|
+
typedef const char *(*node_xpath_fn)(void *);
|
|
90
96
|
static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
|
|
91
97
|
static node_unlink_fn f_node_unlink;
|
|
98
|
+
static traverse_fn f_node_traverse;
|
|
99
|
+
static visit_fn f_node_visit;
|
|
100
|
+
static free_str_fn f_free_str;
|
|
101
|
+
static node_xpath_fn f_node_xpath;
|
|
92
102
|
static set_root_fn f_set_root;
|
|
93
103
|
static doc_free_fn f_doc_free;
|
|
94
104
|
|
|
@@ -105,6 +115,8 @@ static VALUE c_use_after_free_error;
|
|
|
105
115
|
static VALUE native_cache_of(VALUE document);
|
|
106
116
|
static void resolve_binding_classes(void);
|
|
107
117
|
static VALUE binding_cache_of(VALUE document);
|
|
118
|
+
static VALUE binding_klass_for(int kind);
|
|
119
|
+
static VALUE c_iteration_scope;
|
|
108
120
|
|
|
109
121
|
#define NT_ELEMENT 0
|
|
110
122
|
|
|
@@ -200,6 +212,10 @@ static void resolve_symbols(const char *lib_path)
|
|
|
200
212
|
f_elem_set_text = (set_str_fn)lib_sym(h, "leptris_element_set_text");
|
|
201
213
|
f_text_set_content = (set_str_fn)lib_sym(h, "leptris_text_node_set_content");
|
|
202
214
|
f_node_unlink = (node_unlink_fn)lib_sym(h, "leptris_node_unlink");
|
|
215
|
+
f_node_traverse = (traverse_fn)lib_sym(h, "leptris_node_traverse");
|
|
216
|
+
f_node_visit = (visit_fn)lib_sym(h, "leptris_node_visit");
|
|
217
|
+
f_free_str = (free_str_fn)lib_sym(h, "leptris_free_string");
|
|
218
|
+
f_node_xpath = (node_xpath_fn)lib_sym(h, "leptris_node_get_xpath");
|
|
203
219
|
f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
|
|
204
220
|
f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
|
|
205
221
|
if (!f_elem_name || !f_text_content || !f_attr ||
|
|
@@ -211,6 +227,8 @@ static void resolve_symbols(const char *lib_path)
|
|
|
211
227
|
!f_comment_content || !f_cdata_content || !f_pi_target ||
|
|
212
228
|
!f_pi_data || !f_elem_set_name || !f_elem_set_text ||
|
|
213
229
|
!f_text_set_content || !f_node_unlink ||
|
|
230
|
+
!f_node_traverse || !f_node_visit || !f_free_str ||
|
|
231
|
+
!f_node_xpath ||
|
|
214
232
|
!f_doc_free ||
|
|
215
233
|
!f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
|
|
216
234
|
!f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
|
|
@@ -707,6 +725,211 @@ static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
|
|
|
707
725
|
return INT2FIX(st);
|
|
708
726
|
}
|
|
709
727
|
|
|
728
|
+
/* ---- C-yield traversal (TODO.perf/27) ----------------------------
|
|
729
|
+
* Same contracts as the Ruby FFI::Function versions without the
|
|
730
|
+
* per-call closure: traverse is post-order with abort-at-self
|
|
731
|
+
* (the receiver is the LAST node of its subtree in post-order,
|
|
732
|
+
* so stopping at self bounds the walk exactly) and
|
|
733
|
+
* stash-abort-raise exception discipline; visit passes (node,
|
|
734
|
+
* entering, depth). Walk state lives on the caller's C stack —
|
|
735
|
+
* nested traversals are independent. */
|
|
736
|
+
struct trav_state {
|
|
737
|
+
VALUE document;
|
|
738
|
+
VALUE err;
|
|
739
|
+
void *self_ptr;
|
|
740
|
+
int for_visit;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
static VALUE trav_yield_one(VALUE arg)
|
|
744
|
+
{
|
|
745
|
+
return rb_yield(arg);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
static int trav_cb(void *node_ptr, void *user)
|
|
749
|
+
{
|
|
750
|
+
struct trav_state *st = user;
|
|
751
|
+
VALUE cache, key, node;
|
|
752
|
+
int kind, state;
|
|
753
|
+
|
|
754
|
+
if (st->err != Qnil)
|
|
755
|
+
return 1;
|
|
756
|
+
kind = f_node_type(node_ptr);
|
|
757
|
+
cache = binding_cache_of(st->document);
|
|
758
|
+
key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
759
|
+
node = rb_hash_aref(cache, key);
|
|
760
|
+
if (NIL_P(node)) {
|
|
761
|
+
node = rb_obj_alloc(binding_klass_for(kind));
|
|
762
|
+
rb_iv_set(node, "@c_address", key);
|
|
763
|
+
rb_iv_set(node, "@document", st->document);
|
|
764
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
765
|
+
if (rb_obj_class(st->document) == c_iteration_scope) {
|
|
766
|
+
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
767
|
+
rb_iv_set(node, "@native_fast", Qfalse);
|
|
768
|
+
rb_iv_set(node, "@pub_document", Qnil);
|
|
769
|
+
} else {
|
|
770
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
771
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
772
|
+
rb_iv_set(node, "@pub_document", st->document);
|
|
773
|
+
}
|
|
774
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
775
|
+
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
776
|
+
rb_hash_aset(cache, key, node);
|
|
777
|
+
}
|
|
778
|
+
rb_protect(trav_yield_one, node, &state);
|
|
779
|
+
if (state) {
|
|
780
|
+
st->err = rb_errinfo();
|
|
781
|
+
return 1; /* abort the walk */
|
|
782
|
+
}
|
|
783
|
+
return node_ptr == st->self_ptr ? 1 : 0; /* abort-at-self */
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
static VALUE nf_traverse_binding(VALUE self, VALUE document,
|
|
787
|
+
VALUE addr)
|
|
788
|
+
{
|
|
789
|
+
struct trav_state st;
|
|
790
|
+
|
|
791
|
+
(void)self;
|
|
792
|
+
resolve_binding_classes();
|
|
793
|
+
st.document = document;
|
|
794
|
+
st.err = Qnil;
|
|
795
|
+
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
796
|
+
st.for_visit = 0;
|
|
797
|
+
f_node_traverse(st.self_ptr, 1 /* TRAVERSE_POST_ORDER */,
|
|
798
|
+
trav_cb, &st);
|
|
799
|
+
if (st.err != Qnil)
|
|
800
|
+
rb_exc_raise(st.err);
|
|
801
|
+
return Qnil;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
struct visit_yield_args {
|
|
805
|
+
VALUE node, entering, depth;
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
static VALUE trav_yield_args(VALUE arg)
|
|
809
|
+
{
|
|
810
|
+
struct visit_yield_args *a = (struct visit_yield_args *)arg;
|
|
811
|
+
return rb_yield_values(3, a->node, a->entering, a->depth);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* Engine visit callback order is (user, node, entering, depth) —
|
|
815
|
+
* the Ruby closure's |_, node_ptr, entering, depth| mirrors it. */
|
|
816
|
+
static void visit_cb(void *user, void *node_ptr, int entering,
|
|
817
|
+
int depth)
|
|
818
|
+
{
|
|
819
|
+
/* visit's engine signature cannot abort; exceptions are
|
|
820
|
+
* stashed and every later node is skipped — the raise
|
|
821
|
+
* happens after the walk, same observable outcome. */
|
|
822
|
+
struct trav_state *st = user;
|
|
823
|
+
struct visit_yield_args va;
|
|
824
|
+
VALUE cache, key, node;
|
|
825
|
+
int kind, state;
|
|
826
|
+
|
|
827
|
+
if (st->err != Qnil)
|
|
828
|
+
return;
|
|
829
|
+
kind = f_node_type(node_ptr);
|
|
830
|
+
cache = binding_cache_of(st->document);
|
|
831
|
+
key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
832
|
+
node = rb_hash_aref(cache, key);
|
|
833
|
+
if (NIL_P(node)) {
|
|
834
|
+
node = rb_obj_alloc(binding_klass_for(kind));
|
|
835
|
+
rb_iv_set(node, "@c_address", key);
|
|
836
|
+
rb_iv_set(node, "@document", st->document);
|
|
837
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
838
|
+
if (rb_obj_class(st->document) == c_iteration_scope) {
|
|
839
|
+
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
840
|
+
rb_iv_set(node, "@native_fast", Qfalse);
|
|
841
|
+
rb_iv_set(node, "@pub_document", Qnil);
|
|
842
|
+
} else {
|
|
843
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
844
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
845
|
+
rb_iv_set(node, "@pub_document", st->document);
|
|
846
|
+
}
|
|
847
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
848
|
+
rb_iv_set(node, "@node_type", INT2FIX(kind));
|
|
849
|
+
rb_hash_aset(cache, key, node);
|
|
850
|
+
}
|
|
851
|
+
va.node = node;
|
|
852
|
+
va.entering = entering ? Qtrue : Qfalse;
|
|
853
|
+
va.depth = INT2NUM(depth);
|
|
854
|
+
rb_protect(trav_yield_args, (VALUE)&va, &state);
|
|
855
|
+
if (state)
|
|
856
|
+
st->err = rb_errinfo();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
static VALUE nf_visit_binding(VALUE self, VALUE document, VALUE addr)
|
|
860
|
+
{
|
|
861
|
+
struct trav_state st;
|
|
862
|
+
|
|
863
|
+
(void)self;
|
|
864
|
+
resolve_binding_classes();
|
|
865
|
+
st.document = document;
|
|
866
|
+
st.err = Qnil;
|
|
867
|
+
st.self_ptr = (void *)(uintptr_t)NUM2ULL(addr);
|
|
868
|
+
st.for_visit = 1;
|
|
869
|
+
f_node_visit(st.self_ptr, visit_cb, &st);
|
|
870
|
+
if (st.err != Qnil)
|
|
871
|
+
rb_exc_raise(st.err);
|
|
872
|
+
return Qnil;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/* ---- Address-based fills (TODO.perf/28) -------------------------
|
|
876
|
+
* Pure reads of node-local data — no cache, no version — the
|
|
877
|
+
* same shapes the inner_html pass uses. */
|
|
878
|
+
static VALUE nf_fast_text_content(VALUE self, VALUE addr)
|
|
879
|
+
{
|
|
880
|
+
const char *s;
|
|
881
|
+
(void)self;
|
|
882
|
+
s = f_text_content((void *)(uintptr_t)NUM2ULL(addr));
|
|
883
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
static VALUE nf_fast_comment_content(VALUE self, VALUE addr)
|
|
887
|
+
{
|
|
888
|
+
const char *s;
|
|
889
|
+
(void)self;
|
|
890
|
+
s = f_comment_content((void *)(uintptr_t)NUM2ULL(addr));
|
|
891
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
static VALUE nf_fast_cdata_content(VALUE self, VALUE addr)
|
|
895
|
+
{
|
|
896
|
+
const char *s;
|
|
897
|
+
(void)self;
|
|
898
|
+
s = f_cdata_content((void *)(uintptr_t)NUM2ULL(addr));
|
|
899
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
static VALUE nf_fast_pi_target(VALUE self, VALUE addr)
|
|
903
|
+
{
|
|
904
|
+
const char *s;
|
|
905
|
+
(void)self;
|
|
906
|
+
s = f_pi_target((void *)(uintptr_t)NUM2ULL(addr));
|
|
907
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
static VALUE nf_fast_pi_data(VALUE self, VALUE addr)
|
|
911
|
+
{
|
|
912
|
+
const char *s;
|
|
913
|
+
(void)self;
|
|
914
|
+
s = f_pi_data((void *)(uintptr_t)NUM2ULL(addr));
|
|
915
|
+
return s ? rb_utf8_str_new_cstr(s) : Qnil;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/* Owned string: copy + free through the engine's seam (the
|
|
919
|
+
* read_owned_string protocol). */
|
|
920
|
+
static VALUE nf_fast_path(VALUE self, VALUE addr)
|
|
921
|
+
{
|
|
922
|
+
const char *s;
|
|
923
|
+
VALUE out;
|
|
924
|
+
(void)self;
|
|
925
|
+
s = f_node_xpath((void *)(uintptr_t)NUM2ULL(addr));
|
|
926
|
+
if (!s)
|
|
927
|
+
return Qnil;
|
|
928
|
+
out = rb_utf8_str_new_cstr(s);
|
|
929
|
+
f_free_str((void *)s);
|
|
930
|
+
return out;
|
|
931
|
+
}
|
|
932
|
+
|
|
710
933
|
/* C-bound insert family (TODO.perf/14): prepend / insert-after /
|
|
711
934
|
* insert-before share append's gates + predicate + bump shape.
|
|
712
935
|
* mode: 1 prepend, 2 after, 3 before (anchor = receiver). */
|
|
@@ -874,6 +1097,7 @@ static VALUE nn_address(VALUE self)
|
|
|
874
1097
|
static VALUE c_b_element = Qundef, c_b_text, c_b_comment, c_b_cdata,
|
|
875
1098
|
c_b_pi, c_b_node, c_b_result_text, c_b_result_attr,
|
|
876
1099
|
c_b_attr, c_ffi_pointer, c_b_document, c_b_freed;
|
|
1100
|
+
static VALUE c_iteration_scope;
|
|
877
1101
|
static ID id_ptr_new;
|
|
878
1102
|
|
|
879
1103
|
/* Binding classes resolve LAZILY: Init_native can run before the
|
|
@@ -895,6 +1119,7 @@ static void resolve_binding_classes(void)
|
|
|
895
1119
|
c_b_attr = rb_path2class("Leptris::XML::Attr");
|
|
896
1120
|
c_b_document = rb_path2class("Leptris::XML::Document");
|
|
897
1121
|
c_b_freed = rb_path2class("Leptris::XML::Document::Freed");
|
|
1122
|
+
c_iteration_scope = rb_path2class("Leptris::XML::IterationScope");
|
|
898
1123
|
id_ptr_new = rb_intern("new");
|
|
899
1124
|
rb_gc_register_mark_object(c_b_element);
|
|
900
1125
|
rb_gc_register_mark_object(c_b_text);
|
|
@@ -908,6 +1133,7 @@ static void resolve_binding_classes(void)
|
|
|
908
1133
|
rb_gc_register_mark_object(c_b_attr);
|
|
909
1134
|
rb_gc_register_mark_object(c_b_document);
|
|
910
1135
|
rb_gc_register_mark_object(c_b_freed);
|
|
1136
|
+
rb_gc_register_mark_object(c_iteration_scope);
|
|
911
1137
|
}
|
|
912
1138
|
|
|
913
1139
|
static VALUE binding_klass_for(int kind)
|
|
@@ -951,9 +1177,20 @@ static VALUE bulk_children_impl(VALUE document, VALUE parent_addr,
|
|
|
951
1177
|
rb_iv_set(node, "@c_address", key);
|
|
952
1178
|
rb_iv_set(node, "@document", document);
|
|
953
1179
|
rb_iv_set(node, "@parent", Qnil);
|
|
954
|
-
|
|
955
|
-
|
|
1180
|
+
/* TODO.perf/25: scope-aware stamps — the IterationScope
|
|
1181
|
+
* flows through here as "document" for iterparse
|
|
1182
|
+
* elements; those stay memo-exempt and FFI-mutating,
|
|
1183
|
+
* but address-based reads are cache-free and safe. */
|
|
1184
|
+
if (rb_obj_class(document) == c_iteration_scope) {
|
|
1185
|
+
rb_iv_set(node, "@structure_memoizable", Qfalse);
|
|
1186
|
+
rb_iv_set(node, "@native_fast", Qfalse);
|
|
1187
|
+
rb_iv_set(node, "@pub_document", Qnil);
|
|
1188
|
+
} else {
|
|
1189
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1190
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
956
1191
|
rb_iv_set(node, "@pub_document", document);
|
|
1192
|
+
}
|
|
1193
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
957
1194
|
rb_iv_set(node, "@node_type", INT2FIX(kinds[i]));
|
|
958
1195
|
rb_hash_aset(cache, key, node);
|
|
959
1196
|
}
|
|
@@ -989,6 +1226,8 @@ static VALUE m_native_sentinel; /* module object = fallback marker */
|
|
|
989
1226
|
static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
990
1227
|
void *result, void *ptr, int kind,
|
|
991
1228
|
int idx);
|
|
1229
|
+
static VALUE binding_klass_for(int kind);
|
|
1230
|
+
static VALUE c_iteration_scope;
|
|
992
1231
|
|
|
993
1232
|
static VALUE bulk_xpath_array(VALUE document, void *result);
|
|
994
1233
|
|
|
@@ -1697,6 +1936,22 @@ void Init_native(void)
|
|
|
1697
1936
|
nf_set_binding_text, 3);
|
|
1698
1937
|
rb_define_module_function(m_native, "unlink_binding_node",
|
|
1699
1938
|
nf_unlink_binding_node, 2);
|
|
1939
|
+
rb_define_module_function(m_native, "traverse_binding",
|
|
1940
|
+
nf_traverse_binding, 2);
|
|
1941
|
+
rb_define_module_function(m_native, "visit_binding",
|
|
1942
|
+
nf_visit_binding, 2);
|
|
1943
|
+
rb_define_module_function(m_native, "fast_text_content",
|
|
1944
|
+
nf_fast_text_content, 1);
|
|
1945
|
+
rb_define_module_function(m_native, "fast_comment_content",
|
|
1946
|
+
nf_fast_comment_content, 1);
|
|
1947
|
+
rb_define_module_function(m_native, "fast_cdata_content",
|
|
1948
|
+
nf_fast_cdata_content, 1);
|
|
1949
|
+
rb_define_module_function(m_native, "fast_pi_target",
|
|
1950
|
+
nf_fast_pi_target, 1);
|
|
1951
|
+
rb_define_module_function(m_native, "fast_pi_data",
|
|
1952
|
+
nf_fast_pi_data, 1);
|
|
1953
|
+
rb_define_module_function(m_native, "fast_path",
|
|
1954
|
+
nf_fast_path, 1);
|
|
1700
1955
|
rb_define_module_function(m_native, "fast_inner_xml",
|
|
1701
1956
|
nf_fast_inner_xml, 1);
|
|
1702
1957
|
rb_define_module_function(m_native, "doc_handle_attach",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/cdata.rb
CHANGED
|
@@ -6,7 +6,11 @@ class Leptris::XML::CDATA < Leptris::XML::Text
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result =
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_cdata_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_cdata_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
10
14
|
if @document
|
|
11
15
|
@content = result
|
|
12
16
|
@content_version = @document.version
|
data/lib/leptris/xml/comment.rb
CHANGED
|
@@ -6,7 +6,11 @@ class Leptris::XML::Comment < Leptris::XML::Node
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result =
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_comment_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_comment_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
10
14
|
if @document
|
|
11
15
|
@content = result
|
|
12
16
|
@content_version = @document.version
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -15,7 +15,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
15
15
|
def name
|
|
16
16
|
return @name if @name
|
|
17
17
|
ensure_alive!
|
|
18
|
-
@name = if
|
|
18
|
+
@name = if @addr_reads_fast
|
|
19
19
|
Leptris::XML::Native.fast_name(@c_address)
|
|
20
20
|
else
|
|
21
21
|
Leptris::XML::FFI.leptris_element_name(c_ptr)
|
|
@@ -40,7 +40,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
40
40
|
def content
|
|
41
41
|
return @content if memo_hit?(@content_version)
|
|
42
42
|
ensure_alive!
|
|
43
|
-
result = if
|
|
43
|
+
result = if @addr_reads_fast
|
|
44
44
|
Leptris::XML::Native.fast_element_text(@c_address)
|
|
45
45
|
else
|
|
46
46
|
Leptris::XML::FFI.leptris_element_text(c_ptr)
|
|
@@ -57,11 +57,15 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
57
57
|
Leptris::XML::FFI.check_status(
|
|
58
58
|
Leptris::XML::Native.set_binding_text(
|
|
59
59
|
@document, @c_address, new_content.to_s))
|
|
60
|
+
@content = new_content.to_s
|
|
61
|
+
@content_version = @document.version if @document
|
|
60
62
|
return new_content
|
|
61
63
|
end
|
|
62
64
|
ensure_writable!
|
|
63
65
|
Leptris::XML::FFI.check_status(
|
|
64
66
|
Leptris::XML::FFI.leptris_element_set_text(c_ptr, new_content.to_s))
|
|
67
|
+
@content = new_content.to_s
|
|
68
|
+
@content_version = @document.version if @document
|
|
65
69
|
new_content
|
|
66
70
|
end
|
|
67
71
|
|
|
@@ -91,7 +95,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
91
95
|
# engine call resolves it, then the name is cached.
|
|
92
96
|
return v if !v.nil? || @attributes
|
|
93
97
|
ensure_alive!
|
|
94
|
-
v = if
|
|
98
|
+
v = if @addr_reads_fast
|
|
95
99
|
Leptris::XML::Native.fast_attribute(@c_address, name)
|
|
96
100
|
else
|
|
97
101
|
Leptris::XML::FFI.leptris_element_attribute(c_ptr, name)
|
|
@@ -104,7 +108,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
104
108
|
# post-mutation cold [] would stamp the new version onto a
|
|
105
109
|
# stale @attributes hash and attributes would serve it.
|
|
106
110
|
ensure_alive!
|
|
107
|
-
v = if
|
|
111
|
+
v = if @addr_reads_fast
|
|
108
112
|
Leptris::XML::Native.fast_attribute(@c_address, name)
|
|
109
113
|
else
|
|
110
114
|
Leptris::XML::FFI.leptris_element_attribute(c_ptr, name)
|
|
@@ -137,15 +141,39 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
137
141
|
Leptris::XML::FFI.check_status(
|
|
138
142
|
Leptris::XML::Native.set_binding_attribute(
|
|
139
143
|
@document, @c_address, key.to_s, value.to_s))
|
|
144
|
+
seed_attribute_memo(key.to_s, value.to_s)
|
|
140
145
|
return value
|
|
141
146
|
end
|
|
142
147
|
ensure_writable!
|
|
143
148
|
Leptris::XML::FFI.check_status(
|
|
144
149
|
Leptris::XML::FFI.leptris_element_set_attribute(c_ptr, key.to_s, value.to_s))
|
|
150
|
+
seed_attribute_memo(key.to_s, value.to_s)
|
|
145
151
|
value
|
|
146
152
|
end
|
|
147
153
|
alias_method :set_attribute, :[]=
|
|
148
154
|
|
|
155
|
+
# TODO.perf/26: the write knows the new value — seed the
|
|
156
|
+
# version-stamped memo so a read-back skips re-derivation. A
|
|
157
|
+
# valid-and-just-invalidated partial memo extends in place; a
|
|
158
|
+
# full face (or anything older) restarts partial, mirroring the
|
|
159
|
+
# cold-[] rules.
|
|
160
|
+
def seed_attribute_memo(name, written)
|
|
161
|
+
return unless @document
|
|
162
|
+
values = @attr_values
|
|
163
|
+
if values && !@attributes &&
|
|
164
|
+
@attributes_version == @document.version - 1
|
|
165
|
+
values[name] = written
|
|
166
|
+
else
|
|
167
|
+
@attr_values = { name => written }
|
|
168
|
+
@attributes = nil
|
|
169
|
+
@attribute_nodes = nil
|
|
170
|
+
@keys = nil
|
|
171
|
+
@values = nil
|
|
172
|
+
end
|
|
173
|
+
@attributes_version = @document.version
|
|
174
|
+
end
|
|
175
|
+
private :seed_attribute_memo
|
|
176
|
+
|
|
149
177
|
def key?(name)
|
|
150
178
|
ensure_alive!
|
|
151
179
|
# Versioned attribute memo first (TODO.perf/21): a hit proves
|
|
@@ -232,7 +260,9 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
232
260
|
# Require the full Attr-hash face — a cold [] may have set the
|
|
233
261
|
# version with only a partial @attr_values (ruby#150).
|
|
234
262
|
return @attributes if @attributes && memo_hit?(@attributes_version)
|
|
235
|
-
|
|
263
|
+
# TODO.perf/28: the bulk faces touch no document state —
|
|
264
|
+
# scope-eligible via @addr_reads_fast.
|
|
265
|
+
if @addr_reads_fast
|
|
236
266
|
# TODO.perf/10: both memo faces in one C walk.
|
|
237
267
|
result, values = Leptris::XML::Native.bulk_attr_faces(
|
|
238
268
|
@c_address, self)
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -36,6 +36,9 @@ class Leptris::XML::Node
|
|
|
36
36
|
@structure_memoizable =
|
|
37
37
|
!document.nil? && !document.is_a?(Leptris::XML::IterationScope)
|
|
38
38
|
@pub_document = @structure_memoizable ? document : nil
|
|
39
|
+
# TODO.perf/25: pure address-based native reads touch no
|
|
40
|
+
# cache — safe for scope-owned (iterparse) elements too.
|
|
41
|
+
@addr_reads_fast = !!defined?(Leptris::XML::NATIVE_FAST)
|
|
39
42
|
# NATIVE_FAST availability is fixed at load time before any
|
|
40
43
|
# node exists, so the conjunction with document-ownership is
|
|
41
44
|
# construct-time constant — the hot gates read one ivar.
|
|
@@ -164,6 +167,16 @@ class Leptris::XML::Node
|
|
|
164
167
|
@native_fast
|
|
165
168
|
end
|
|
166
169
|
|
|
170
|
+
# TODO.perf/25: the bulk children faces accept the
|
|
171
|
+
# IterationScope itself (its @wrapper_cache resets per yield —
|
|
172
|
+
# recycled addresses cannot collide — and its @version advances
|
|
173
|
+
# per yield and on mutation), so streaming children ride the
|
|
174
|
+
# one-pass materialization too. The scope-aware stamps live in
|
|
175
|
+
# the C face.
|
|
176
|
+
def native_bulk_children?
|
|
177
|
+
defined?(Leptris::XML::NATIVE_FAST)
|
|
178
|
+
end
|
|
179
|
+
|
|
167
180
|
# Content-defined 64-bit Merkle digest of this subtree
|
|
168
181
|
# (libleptris 1.9.99, #869): element name/prefix/resolved
|
|
169
182
|
# namespace URI, attributes sorted and first-wins-deduplicated,
|
|
@@ -318,7 +331,7 @@ class Leptris::XML::Node
|
|
|
318
331
|
# (leptris_node_children_ex), so no per-child get_type.
|
|
319
332
|
return @children if memo_hit?(@children_version)
|
|
320
333
|
ensure_alive!
|
|
321
|
-
if
|
|
334
|
+
if native_bulk_children?
|
|
322
335
|
# TODO.perf/01 tail: one C pass constructs every binding
|
|
323
336
|
# wrapper (class dispatch + ivars + identity cache) — the
|
|
324
337
|
# per-child Ruby wrap frames disappear.
|
|
@@ -419,7 +432,7 @@ class Leptris::XML::Node
|
|
|
419
432
|
# children are never wrapped (nor their get_type paid — the
|
|
420
433
|
# ELEMENT hint rides along). Other nodes keep the filter.
|
|
421
434
|
result =
|
|
422
|
-
if
|
|
435
|
+
if native_bulk_children?
|
|
423
436
|
Leptris::XML::Native.bulk_element_children(@document, @c_address)
|
|
424
437
|
elsif is_a?(Leptris::XML::Element)
|
|
425
438
|
parent = as_element_or_self
|
|
@@ -479,6 +492,13 @@ class Leptris::XML::Node
|
|
|
479
492
|
def visit(&block)
|
|
480
493
|
return enum_for(:visit) unless block
|
|
481
494
|
ensure_alive!
|
|
495
|
+
# TODO.perf/27: the ext's callback rb_yields directly — no
|
|
496
|
+
# FFI::Function closure per call.
|
|
497
|
+
if @addr_reads_fast
|
|
498
|
+
Leptris::XML::Native.visit_binding(@document, @c_address,
|
|
499
|
+
&block)
|
|
500
|
+
return self
|
|
501
|
+
end
|
|
482
502
|
document = @document
|
|
483
503
|
visitor = ::FFI::Function.new(
|
|
484
504
|
:void, [:pointer, :pointer, :int, :int], blocking: true) do |_, node_ptr, entering, depth|
|
|
@@ -511,9 +531,18 @@ class Leptris::XML::Node
|
|
|
511
531
|
# exception and returns non-zero, aborting the C walk — without
|
|
512
532
|
# it the FFI dispatch silently swallowed the exception and the
|
|
513
533
|
# walk continued with partially processed data.
|
|
514
|
-
def traverse
|
|
515
|
-
return enum_for(:traverse) unless
|
|
534
|
+
def traverse(&block)
|
|
535
|
+
return enum_for(:traverse) unless block
|
|
516
536
|
ensure_alive!
|
|
537
|
+
# TODO.perf/27: post-order + abort-at-self + stash-abort-raise
|
|
538
|
+
# all preserved in the C callback (walk state on its stack).
|
|
539
|
+
# The block forwards explicitly — rb_yield needs it on the C
|
|
540
|
+
# entry's own frame.
|
|
541
|
+
if @addr_reads_fast
|
|
542
|
+
Leptris::XML::Native.traverse_binding(@document, @c_address,
|
|
543
|
+
&block)
|
|
544
|
+
return self
|
|
545
|
+
end
|
|
517
546
|
error = nil
|
|
518
547
|
self_address = @c_address
|
|
519
548
|
callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _|
|
|
@@ -534,8 +563,12 @@ class Leptris::XML::Node
|
|
|
534
563
|
def path
|
|
535
564
|
return @path if memo_hit?(@path_version)
|
|
536
565
|
ensure_alive!
|
|
537
|
-
|
|
538
|
-
|
|
566
|
+
result = if @addr_reads_fast
|
|
567
|
+
Leptris::XML::Native.fast_path(@c_address)
|
|
568
|
+
else
|
|
569
|
+
str_ptr = Leptris::XML::FFI.leptris_node_get_xpath(c_ptr)
|
|
570
|
+
str_ptr.null? ? nil : Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
571
|
+
end
|
|
539
572
|
if @document
|
|
540
573
|
@path = result
|
|
541
574
|
@path_version = @document.version
|
|
@@ -4,7 +4,11 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
4
4
|
def name
|
|
5
5
|
return @name if memo_hit?(@name_version)
|
|
6
6
|
ensure_alive!
|
|
7
|
-
result =
|
|
7
|
+
result = if @addr_reads_fast
|
|
8
|
+
Leptris::XML::Native.fast_pi_target(@c_address)
|
|
9
|
+
else
|
|
10
|
+
Leptris::XML::FFI.leptris_pi_node_get_target(c_ptr)
|
|
11
|
+
end
|
|
8
12
|
if @document
|
|
9
13
|
@name = result
|
|
10
14
|
@name_version = @document.version
|
|
@@ -16,8 +20,12 @@ class Leptris::XML::ProcessingInstruction < Leptris::XML::Node
|
|
|
16
20
|
def content
|
|
17
21
|
return @content if memo_hit?(@content_version)
|
|
18
22
|
ensure_alive!
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
raw = if @addr_reads_fast
|
|
24
|
+
Leptris::XML::Native.fast_pi_data(@c_address)
|
|
25
|
+
else
|
|
26
|
+
Leptris::XML::FFI.leptris_pi_node_get_data(c_ptr)
|
|
27
|
+
end
|
|
28
|
+
result = Leptris::XML::FFI.read_pi_data(raw).to_s
|
|
21
29
|
if @document
|
|
22
30
|
@content = result
|
|
23
31
|
@content_version = @document.version
|
data/lib/leptris/xml/text.rb
CHANGED
|
@@ -6,7 +6,11 @@ class Leptris::XML::Text < Leptris::XML::Node
|
|
|
6
6
|
def content
|
|
7
7
|
return @content if memo_hit?(@content_version)
|
|
8
8
|
ensure_alive!
|
|
9
|
-
result =
|
|
9
|
+
result = if @addr_reads_fast
|
|
10
|
+
Leptris::XML::Native.fast_text_content(@c_address)
|
|
11
|
+
else
|
|
12
|
+
Leptris::XML::FFI.leptris_text_node_get_content(c_ptr)
|
|
13
|
+
end
|
|
10
14
|
if @document
|
|
11
15
|
@content = result
|
|
12
16
|
@content_version = @document.version
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.174.
|
|
4
|
+
version: 1.9.174.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -93,6 +93,10 @@ files:
|
|
|
93
93
|
- TODO.perf/22-eager-nodeset-materialization.md
|
|
94
94
|
- TODO.perf/23-cbound-value-mutations.md
|
|
95
95
|
- TODO.perf/24-immutable-read-lanes.md
|
|
96
|
+
- TODO.perf/25-scope-owned-bulk-path.md
|
|
97
|
+
- TODO.perf/26-post-mutation-memo-seeding.md
|
|
98
|
+
- TODO.perf/27-c-yield-traversal.md
|
|
99
|
+
- TODO.perf/28-address-fills.md
|
|
96
100
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
97
101
|
- TODO.restructure/02-deep-copy-seam.md
|
|
98
102
|
- TODO.restructure/03-evaluation-context-seam.md
|