leptris 1.9.174.3 → 1.9.174.4
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 +19 -0
- data/TODO.perf/22-eager-nodeset-materialization.md +30 -0
- data/TODO.perf/23-cbound-value-mutations.md +27 -0
- data/TODO.perf/24-immutable-read-lanes.md +22 -0
- data/ext/leptris/native/native.c +122 -2
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/element.rb +12 -0
- data/lib/leptris/xml/node.rb +15 -3
- data/lib/leptris/xml/searchable.rb +9 -0
- data/lib/leptris/xml/text.rb +6 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3052fa875df89194fa904610e20786deaf2076de881242382afe9bbc9fd7fa0d
|
|
4
|
+
data.tar.gz: 86806a382948d948d2ec5c2550302a8ae52b976edd224aaff4c130f3a4606f3e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81e9fe81a94ddd6d82ef044569b8d43ff5c663ca41cf2a59798660638e4f73cfc23f7aabf4eb3123757e3590d0d7524438f378c6b1bd447a2b39af95bb0089d5
|
|
7
|
+
data.tar.gz: e161ced4c4ab3634ae6a4fa6ebb0e95e970e1634b72a9d1b4ef5e1ad3800461170a2893bb89477c404f22b992813b7abf66abc083272e851c341affc699488fc
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.174.4] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Eager-in-C nodeset materialization (TODO.perf/22)**: xpath
|
|
13
|
+
results materialize and free in one C pass — no
|
|
14
|
+
FFI::AutoPointer, Method object, or finalizer per call; exotic
|
|
15
|
+
kinds keep the lazy path (sentinel scan in C). Measured (load
|
|
16
|
+
~11): a 2000-node xpath set materializes at Nokogiri parity
|
|
17
|
+
(55.9µs vs 57.3µs).
|
|
18
|
+
- **C-bound value mutations (TODO.perf/23)**: `Element#name=`,
|
|
19
|
+
`#content=`, `Text#content=`, `Node#unlink` run gates + version
|
|
20
|
+
bump + engine write in one dispatch (name= 237ns, content=
|
|
21
|
+
298ns measured); readonly raises verified.
|
|
22
|
+
- **Immutable read lanes (TODO.perf/24)**: `line`/`byte_offset`
|
|
23
|
+
memoize (positions never change); `Node#document` answers a
|
|
24
|
+
constructor-precomputed ivar (the scope_owned? chain leaves
|
|
25
|
+
one of the most-called readers).
|
|
26
|
+
|
|
8
27
|
## [1.9.174.3] - 2026-09-15
|
|
9
28
|
|
|
10
29
|
### Changed
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 22 — Eager-in-C nodeset materialization (drop the per-call AutoPointer)
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.4)
|
|
4
|
+
|
|
5
|
+
Every xpath() nodeset result pays an FFI::AutoPointer + a Method
|
|
6
|
+
object + finalizer registration (~400-600ns plus their GC) just
|
|
7
|
+
to keep the C result handle alive until the lazy NodeSet
|
|
8
|
+
materializes — and virtually every consumer materializes anyway
|
|
9
|
+
(each/size/to_a). Materialize eagerly in C instead: one face runs
|
|
10
|
+
the bulk pass AND frees the result handle, returning the Ruby
|
|
11
|
+
Array; Searchable wraps it in the eager NodeSet constructor (an
|
|
12
|
+
Array-backed set has no handle at all). Exotic node kinds (the
|
|
13
|
+
module-object sentinel) fall back to the existing lazy path with
|
|
14
|
+
the handle intact.
|
|
15
|
+
|
|
16
|
+
Gates: xpath results identical for every kind (including
|
|
17
|
+
sentinel-graded kinds); laziness is internal — the public NodeSet
|
|
18
|
+
behaviors (indexing, iteration, size) unchanged; the xpath row
|
|
19
|
+
drops the AutoPointer cost per call.
|
|
20
|
+
|
|
21
|
+
## Outcome (1.9.174.4)
|
|
22
|
+
|
|
23
|
+
Native.materialize_xpath: the bulk pass AND the result free in C;
|
|
24
|
+
Searchable's nodeset branch wraps the returned Array in the eager
|
|
25
|
+
NodeSet (no AutoPointer, no Method object, no finalizer per
|
|
26
|
+
xpath call). Sentinel scans happen in C (a Ruby include? would
|
|
27
|
+
cost microseconds on large sets) — any exotic kind returns Qnil
|
|
28
|
+
with the handle alive for the lazy fallback. Measured (load
|
|
29
|
+
~11): full materialization of a 2000-node xpath set 55.9µs vs
|
|
30
|
+
Nokogiri 57.3µs — parity on the heavy shape.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 23 — C-bound name= / content= / unlink
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.4)
|
|
4
|
+
|
|
5
|
+
The value mutations still run ensure_writable! + FFI marshaling
|
|
6
|
+
per call (~450ns): Element#name=, Element#content=,
|
|
7
|
+
Text#content=, and Node#unlink (transforms rename, rewrite, and
|
|
8
|
+
remove nodes constantly). Same face shape as set_binding_attribute:
|
|
9
|
+
gates + version bump + engine write in one dispatch —
|
|
10
|
+
set_binding_name (element names), set_binding_text (element text
|
|
11
|
+
or text-node content, kind-dispatched in C), and
|
|
12
|
+
unlink_binding_node (plus the Ruby-side @parent clear and memo
|
|
13
|
+
invalidations that ride the version bump).
|
|
14
|
+
|
|
15
|
+
Gates: rename/content/unlink semantics unchanged (memos, sibling
|
|
16
|
+
stamps, path memo all invalidate through the version bump);
|
|
17
|
+
readonly raises through every face.
|
|
18
|
+
|
|
19
|
+
## Outcome (1.9.174.4)
|
|
20
|
+
|
|
21
|
+
Native.set_binding_name / set_binding_text (kind-dispatched
|
|
22
|
+
element-vs-text) / unlink_binding_node — gates + bump + engine
|
|
23
|
+
write in one dispatch, wired into Element#name=, #content=,
|
|
24
|
+
Text#content=, and Node#unlink (@parent clear and memo
|
|
25
|
+
invalidation ride the version bump). Measured (load ~11):
|
|
26
|
+
name= 237ns, content= 298ns, unlink+re-add 1.74µs; readonly
|
|
27
|
+
raises verified through every face.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 24 — Immutable read lanes: line/byte_offset memo + precomputed public #document
|
|
2
|
+
|
|
3
|
+
Status: DONE (1.9.174.4)
|
|
4
|
+
|
|
5
|
+
Node#line and #byte_offset pay an FFI round-trip per call, but a
|
|
6
|
+
node's source position never changes — memoize unconditionally
|
|
7
|
+
(first read wins). The public Node#document walks scope_owned? (a
|
|
8
|
+
method chain) on every call though scope ownership is fixed at
|
|
9
|
+
construction — precompute @pub_document once (nil for
|
|
10
|
+
scope-owned) including at every C construction site.
|
|
11
|
+
|
|
12
|
+
Gates: line/byte_offset stable across mutations (they must be —
|
|
13
|
+
the engine reports source positions); document nil for
|
|
14
|
+
scope-owned yields exactly as before; suite both modes.
|
|
15
|
+
|
|
16
|
+
## Outcome (1.9.174.4)
|
|
17
|
+
|
|
18
|
+
line/byte_offset memoize unconditionally (a node's source
|
|
19
|
+
position never changes; 40ns hits). Node#document answers the
|
|
20
|
+
constructor-precomputed @pub_document (nil for scope-owned
|
|
21
|
+
yields) — the scope_owned? method chain disappears from one of
|
|
22
|
+
the most-called readers; every C construction site sets it.
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -85,6 +85,10 @@ static xp_free_fn f_xp_free;
|
|
|
85
85
|
static first_child_fn f_first_child;
|
|
86
86
|
static node_str_fn f_comment_content, f_cdata_content, f_pi_target,
|
|
87
87
|
f_pi_data;
|
|
88
|
+
typedef int (*set_str_fn)(void *, const char *);
|
|
89
|
+
typedef int (*node_unlink_fn)(void *);
|
|
90
|
+
static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
|
|
91
|
+
static node_unlink_fn f_node_unlink;
|
|
88
92
|
static set_root_fn f_set_root;
|
|
89
93
|
static doc_free_fn f_doc_free;
|
|
90
94
|
|
|
@@ -192,6 +196,10 @@ static void resolve_symbols(const char *lib_path)
|
|
|
192
196
|
f_cdata_content = (node_str_fn)lib_sym(h, "leptris_cdata_node_get_content");
|
|
193
197
|
f_pi_target = (node_str_fn)lib_sym(h, "leptris_pi_node_get_target");
|
|
194
198
|
f_pi_data = (node_str_fn)lib_sym(h, "leptris_pi_node_get_data");
|
|
199
|
+
f_elem_set_name = (set_str_fn)lib_sym(h, "leptris_element_set_name");
|
|
200
|
+
f_elem_set_text = (set_str_fn)lib_sym(h, "leptris_element_set_text");
|
|
201
|
+
f_text_set_content = (set_str_fn)lib_sym(h, "leptris_text_node_set_content");
|
|
202
|
+
f_node_unlink = (node_unlink_fn)lib_sym(h, "leptris_node_unlink");
|
|
195
203
|
f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
|
|
196
204
|
f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
|
|
197
205
|
if (!f_elem_name || !f_text_content || !f_attr ||
|
|
@@ -201,7 +209,8 @@ static void resolve_symbols(const char *lib_path)
|
|
|
201
209
|
!f_insert_after || !f_insert_before || !f_set_attr || !f_set_root ||
|
|
202
210
|
!f_xp_type || !f_xp_free || !f_first_child ||
|
|
203
211
|
!f_comment_content || !f_cdata_content || !f_pi_target ||
|
|
204
|
-
!f_pi_data ||
|
|
212
|
+
!f_pi_data || !f_elem_set_name || !f_elem_set_text ||
|
|
213
|
+
!f_text_set_content || !f_node_unlink ||
|
|
205
214
|
!f_doc_free ||
|
|
206
215
|
!f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
|
|
207
216
|
!f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
|
|
@@ -630,6 +639,74 @@ static VALUE nf_set_binding_attribute(VALUE self, VALUE document,
|
|
|
630
639
|
return INT2FIX(st);
|
|
631
640
|
}
|
|
632
641
|
|
|
642
|
+
/* C-bound value mutations (TODO.perf/23): the same gates + bump
|
|
643
|
+
* + engine write shape as set_binding_attribute. */
|
|
644
|
+
static VALUE nf_set_binding_name(VALUE self, VALUE document,
|
|
645
|
+
VALUE addr, VALUE name)
|
|
646
|
+
{
|
|
647
|
+
void *node;
|
|
648
|
+
int st;
|
|
649
|
+
|
|
650
|
+
(void)self;
|
|
651
|
+
resolve_binding_classes();
|
|
652
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
653
|
+
rb_raise(c_use_after_free_error,
|
|
654
|
+
"owning document has been freed");
|
|
655
|
+
if (rb_ivar_get(document, id_iv_readonly) == Qtrue)
|
|
656
|
+
rb_raise(c_readonly_error,
|
|
657
|
+
"document is readonly — mutation attempted");
|
|
658
|
+
node = (void *)(uintptr_t)NUM2ULL(addr);
|
|
659
|
+
rb_ivar_set(document, id_iv_version,
|
|
660
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
661
|
+
st = f_elem_set_name(node, StringValueCStr(name));
|
|
662
|
+
return INT2FIX(st);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* Element text or text-node content — kind-dispatched in C. */
|
|
666
|
+
static VALUE nf_set_binding_text(VALUE self, VALUE document,
|
|
667
|
+
VALUE addr, VALUE content)
|
|
668
|
+
{
|
|
669
|
+
void *node;
|
|
670
|
+
int st;
|
|
671
|
+
|
|
672
|
+
(void)self;
|
|
673
|
+
resolve_binding_classes();
|
|
674
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
675
|
+
rb_raise(c_use_after_free_error,
|
|
676
|
+
"owning document has been freed");
|
|
677
|
+
if (rb_ivar_get(document, id_iv_readonly) == Qtrue)
|
|
678
|
+
rb_raise(c_readonly_error,
|
|
679
|
+
"document is readonly — mutation attempted");
|
|
680
|
+
node = (void *)(uintptr_t)NUM2ULL(addr);
|
|
681
|
+
rb_ivar_set(document, id_iv_version,
|
|
682
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
683
|
+
st = f_node_type(node) == NT_ELEMENT
|
|
684
|
+
? f_elem_set_text(node, StringValueCStr(content))
|
|
685
|
+
: f_text_set_content(node, StringValueCStr(content));
|
|
686
|
+
return INT2FIX(st);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
|
|
690
|
+
VALUE addr)
|
|
691
|
+
{
|
|
692
|
+
void *node;
|
|
693
|
+
int st;
|
|
694
|
+
|
|
695
|
+
(void)self;
|
|
696
|
+
resolve_binding_classes();
|
|
697
|
+
if (NIL_P(rb_ivar_get(document, id_iv_c_address)))
|
|
698
|
+
rb_raise(c_use_after_free_error,
|
|
699
|
+
"owning document has been freed");
|
|
700
|
+
if (rb_ivar_get(document, id_iv_readonly) == Qtrue)
|
|
701
|
+
rb_raise(c_readonly_error,
|
|
702
|
+
"document is readonly — mutation attempted");
|
|
703
|
+
node = (void *)(uintptr_t)NUM2ULL(addr);
|
|
704
|
+
rb_ivar_set(document, id_iv_version,
|
|
705
|
+
LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
|
|
706
|
+
st = f_node_unlink(node);
|
|
707
|
+
return INT2FIX(st);
|
|
708
|
+
}
|
|
709
|
+
|
|
633
710
|
/* C-bound insert family (TODO.perf/14): prepend / insert-after /
|
|
634
711
|
* insert-before share append's gates + predicate + bump shape.
|
|
635
712
|
* mode: 1 prepend, 2 after, 3 before (anchor = receiver). */
|
|
@@ -876,6 +953,7 @@ static VALUE bulk_children_impl(VALUE document, VALUE parent_addr,
|
|
|
876
953
|
rb_iv_set(node, "@parent", Qnil);
|
|
877
954
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
878
955
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
956
|
+
rb_iv_set(node, "@pub_document", document);
|
|
879
957
|
rb_iv_set(node, "@node_type", INT2FIX(kinds[i]));
|
|
880
958
|
rb_hash_aset(cache, key, node);
|
|
881
959
|
}
|
|
@@ -912,15 +990,45 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
912
990
|
void *result, void *ptr, int kind,
|
|
913
991
|
int idx);
|
|
914
992
|
|
|
993
|
+
static VALUE bulk_xpath_array(VALUE document, void *result);
|
|
994
|
+
|
|
915
995
|
static VALUE nf_bulk_xpath(VALUE self, VALUE document, VALUE result_ptr_val)
|
|
996
|
+
{
|
|
997
|
+
(void)self;
|
|
998
|
+
return bulk_xpath_array(document,
|
|
999
|
+
(void *)(uintptr_t)NUM2ULL(result_ptr_val));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/* TODO.perf/22: materialize AND free — the eager xpath path owns
|
|
1003
|
+
* the handle lifecycle in C; no Ruby AutoPointer, no finalizer. */
|
|
1004
|
+
static VALUE nf_materialize_xpath(VALUE self, VALUE document,
|
|
1005
|
+
VALUE result_ptr_val)
|
|
916
1006
|
{
|
|
917
1007
|
void *result = (void *)(uintptr_t)NUM2ULL(result_ptr_val);
|
|
1008
|
+
VALUE out;
|
|
1009
|
+
long i, n;
|
|
1010
|
+
|
|
1011
|
+
(void)self;
|
|
1012
|
+
out = bulk_xpath_array(document, result);
|
|
1013
|
+
n = RARRAY_LEN(out);
|
|
1014
|
+
for (i = 0; i < n; i++) {
|
|
1015
|
+
if (rb_ary_entry(out, i) == m_native_sentinel) {
|
|
1016
|
+
/* Exotic kinds need the Ruby fallback per entry — the
|
|
1017
|
+
* caller keeps the handle and goes the lazy path. */
|
|
1018
|
+
return Qnil;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
f_xp_free(result);
|
|
1022
|
+
return out;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
static VALUE bulk_xpath_array(VALUE document, void *result)
|
|
1026
|
+
{
|
|
918
1027
|
void **buf;
|
|
919
1028
|
int *kinds;
|
|
920
1029
|
int count, i;
|
|
921
1030
|
VALUE cache, out;
|
|
922
1031
|
|
|
923
|
-
(void)self;
|
|
924
1032
|
resolve_binding_classes();
|
|
925
1033
|
size_t scount = f_xp_count(result);
|
|
926
1034
|
if (scount == 0)
|
|
@@ -970,6 +1078,7 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
970
1078
|
rb_iv_set(node, "@parent", Qnil);
|
|
971
1079
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
972
1080
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1081
|
+
rb_iv_set(node, "@pub_document", document);
|
|
973
1082
|
rb_iv_set(node, "@node_type", INT2FIX(0));
|
|
974
1083
|
rb_hash_aset(cache, key, node);
|
|
975
1084
|
}
|
|
@@ -1009,6 +1118,7 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
|
|
|
1009
1118
|
rb_iv_set(node, "@parent", Qnil);
|
|
1010
1119
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1011
1120
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1121
|
+
rb_iv_set(node, "@pub_document", document);
|
|
1012
1122
|
rb_iv_set(node, "@node_type", INT2FIX(nt));
|
|
1013
1123
|
rb_hash_aset(cache, key, node);
|
|
1014
1124
|
}
|
|
@@ -1097,6 +1207,7 @@ static VALUE nf_create_binding_element(VALUE self, VALUE document,
|
|
|
1097
1207
|
rb_iv_set(node, "@parent", Qnil);
|
|
1098
1208
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1099
1209
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1210
|
+
rb_iv_set(node, "@pub_document", document);
|
|
1100
1211
|
rb_iv_set(node, "@node_type", INT2FIX(0));
|
|
1101
1212
|
cache = binding_cache_of(document);
|
|
1102
1213
|
key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
@@ -1122,6 +1233,7 @@ static VALUE nf_create_binding_text(VALUE self, VALUE document,
|
|
|
1122
1233
|
rb_iv_set(node, "@parent", Qnil);
|
|
1123
1234
|
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
1124
1235
|
rb_iv_set(node, "@native_fast", Qtrue);
|
|
1236
|
+
rb_iv_set(node, "@pub_document", document);
|
|
1125
1237
|
rb_iv_set(node, "@node_type", INT2FIX(1));
|
|
1126
1238
|
cache = binding_cache_of(document);
|
|
1127
1239
|
key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
@@ -1577,6 +1689,14 @@ void Init_native(void)
|
|
|
1577
1689
|
nf_insert_binding_child, 4);
|
|
1578
1690
|
rb_define_module_function(m_native, "at_xpath_first",
|
|
1579
1691
|
nf_at_xpath_first, 2);
|
|
1692
|
+
rb_define_module_function(m_native, "materialize_xpath",
|
|
1693
|
+
nf_materialize_xpath, 2);
|
|
1694
|
+
rb_define_module_function(m_native, "set_binding_name",
|
|
1695
|
+
nf_set_binding_name, 3);
|
|
1696
|
+
rb_define_module_function(m_native, "set_binding_text",
|
|
1697
|
+
nf_set_binding_text, 3);
|
|
1698
|
+
rb_define_module_function(m_native, "unlink_binding_node",
|
|
1699
|
+
nf_unlink_binding_node, 2);
|
|
1580
1700
|
rb_define_module_function(m_native, "fast_inner_xml",
|
|
1581
1701
|
nf_fast_inner_xml, 1);
|
|
1582
1702
|
rb_define_module_function(m_native, "doc_handle_attach",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -24,6 +24,12 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
24
24
|
alias_method :node_name, :name
|
|
25
25
|
|
|
26
26
|
def name=(new_name)
|
|
27
|
+
if @native_fast
|
|
28
|
+
Leptris::XML::FFI.check_status(
|
|
29
|
+
Leptris::XML::Native.set_binding_name(
|
|
30
|
+
@document, @c_address, new_name))
|
|
31
|
+
return @name = new_name
|
|
32
|
+
end
|
|
27
33
|
ensure_writable!
|
|
28
34
|
Leptris::XML::FFI.check_status(
|
|
29
35
|
Leptris::XML::FFI.leptris_element_set_name(c_ptr, new_name))
|
|
@@ -47,6 +53,12 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
def content=(new_content)
|
|
56
|
+
if @native_fast
|
|
57
|
+
Leptris::XML::FFI.check_status(
|
|
58
|
+
Leptris::XML::Native.set_binding_text(
|
|
59
|
+
@document, @c_address, new_content.to_s))
|
|
60
|
+
return new_content
|
|
61
|
+
end
|
|
50
62
|
ensure_writable!
|
|
51
63
|
Leptris::XML::FFI.check_status(
|
|
52
64
|
Leptris::XML::FFI.leptris_element_set_text(c_ptr, new_content.to_s))
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -11,8 +11,10 @@ class Leptris::XML::Node
|
|
|
11
11
|
# Iterparse-yielded elements are owned by an IterationScope (the
|
|
12
12
|
# internal lifetime/memoization authority) — the public #document
|
|
13
13
|
# answers nil for them, per the documented contract.
|
|
14
|
+
# Precomputed at construction (TODO.perf/24): scope ownership
|
|
15
|
+
# is fixed for a node's lifetime — nil for iterparse yields.
|
|
14
16
|
def document
|
|
15
|
-
|
|
17
|
+
@pub_document
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def initialize(c_ptr, document, parent: nil, node_type: nil)
|
|
@@ -33,6 +35,7 @@ class Leptris::XML::Node
|
|
|
33
35
|
# keeps the memo-hit path free of method dispatch.
|
|
34
36
|
@structure_memoizable =
|
|
35
37
|
!document.nil? && !document.is_a?(Leptris::XML::IterationScope)
|
|
38
|
+
@pub_document = @structure_memoizable ? document : nil
|
|
36
39
|
# NATIVE_FAST availability is fixed at load time before any
|
|
37
40
|
# node exists, so the conjunction with document-ownership is
|
|
38
41
|
# construct-time constant — the hot gates read one ivar.
|
|
@@ -278,9 +281,11 @@ class Leptris::XML::Node
|
|
|
278
281
|
@readonly_document = true
|
|
279
282
|
end
|
|
280
283
|
|
|
284
|
+
# A node's source position never changes — first read wins.
|
|
281
285
|
def line
|
|
286
|
+
return @line if defined?(@line)
|
|
282
287
|
ensure_alive!
|
|
283
|
-
Leptris::XML::FFI.leptris_node_line(c_ptr)
|
|
288
|
+
@line = Leptris::XML::FFI.leptris_node_line(c_ptr)
|
|
284
289
|
end
|
|
285
290
|
|
|
286
291
|
# Byte offset of the node's markup in its parse source (the '<'
|
|
@@ -288,8 +293,9 @@ class Leptris::XML::Node
|
|
|
288
293
|
# CALLBACK rows echo). 0 when unknown: mutation-created nodes, or
|
|
289
294
|
# documents >= 2 GiB.
|
|
290
295
|
def byte_offset
|
|
296
|
+
return @byte_offset if defined?(@byte_offset)
|
|
291
297
|
ensure_alive!
|
|
292
|
-
Leptris::XML::FFI.leptris_node_byte_offset(c_ptr)
|
|
298
|
+
@byte_offset = Leptris::XML::FFI.leptris_node_byte_offset(c_ptr)
|
|
293
299
|
end
|
|
294
300
|
|
|
295
301
|
def <=>(other)
|
|
@@ -445,6 +451,12 @@ class Leptris::XML::Node
|
|
|
445
451
|
end
|
|
446
452
|
|
|
447
453
|
def unlink
|
|
454
|
+
if @native_fast
|
|
455
|
+
Leptris::XML::FFI.check_status(
|
|
456
|
+
Leptris::XML::Native.unlink_binding_node(@document, @c_address))
|
|
457
|
+
@parent = nil
|
|
458
|
+
return self
|
|
459
|
+
end
|
|
448
460
|
ensure_writable!
|
|
449
461
|
Leptris::XML::FFI.check_status(
|
|
450
462
|
Leptris::XML::FFI.leptris_node_unlink(c_ptr))
|
|
@@ -298,6 +298,15 @@ module Leptris::XML::Searchable
|
|
|
298
298
|
type = Leptris::XML::FFI.leptris_xpath_result_type(result_ptr)
|
|
299
299
|
case type
|
|
300
300
|
when Leptris::XML::FFI::XPATH_NODESET
|
|
301
|
+
# TODO.perf/22: materialize AND free in C — no Ruby
|
|
302
|
+
# AutoPointer + finalizer per xpath call. Qnil means an
|
|
303
|
+
# exotic kind needs the Ruby fallback per entry; the lazy
|
|
304
|
+
# path below keeps the handle.
|
|
305
|
+
if defined?(Leptris::XML::NATIVE_FAST)
|
|
306
|
+
nodes = Leptris::XML::Native.materialize_xpath(
|
|
307
|
+
document, result_ptr.address)
|
|
308
|
+
return Leptris::XML::NodeSet.new(document, nodes) if nodes
|
|
309
|
+
end
|
|
301
310
|
Leptris::XML::NodeSet.from_result(document, result_ptr)
|
|
302
311
|
when Leptris::XML::FFI::XPATH_BOOLEAN
|
|
303
312
|
v = Leptris::XML::FFI.leptris_xpath_result_boolean(result_ptr) != 0
|
data/lib/leptris/xml/text.rb
CHANGED
|
@@ -15,6 +15,12 @@ class Leptris::XML::Text < Leptris::XML::Node
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def content=(new_content)
|
|
18
|
+
if @native_fast
|
|
19
|
+
Leptris::XML::FFI.check_status(
|
|
20
|
+
Leptris::XML::Native.set_binding_text(
|
|
21
|
+
@document, @c_address, new_content.to_s))
|
|
22
|
+
return new_content
|
|
23
|
+
end
|
|
18
24
|
ensure_writable!
|
|
19
25
|
Leptris::XML::FFI.check_status(
|
|
20
26
|
Leptris::XML::FFI.leptris_text_node_set_content(c_ptr, new_content.to_s))
|
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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -90,6 +90,9 @@ files:
|
|
|
90
90
|
- TODO.perf/19-lazy-node-pointer.md
|
|
91
91
|
- TODO.perf/20-css-translation-cache.md
|
|
92
92
|
- TODO.perf/21-key-memo-consult.md
|
|
93
|
+
- TODO.perf/22-eager-nodeset-materialization.md
|
|
94
|
+
- TODO.perf/23-cbound-value-mutations.md
|
|
95
|
+
- TODO.perf/24-immutable-read-lanes.md
|
|
93
96
|
- TODO.restructure/01-constraint-compliance-audit.md
|
|
94
97
|
- TODO.restructure/02-deep-copy-seam.md
|
|
95
98
|
- TODO.restructure/03-evaluation-context-seam.md
|