leptris 1.9.174.5 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec76aed6663e2ae97d677c09e8b05486eb6e0543da0f92640a81a87d49c38565
4
- data.tar.gz: bd881b0e87ff21939925f879abfced736f304a5396f675013a2bc8e2f220a89c
3
+ metadata.gz: 0ac7143c5a7730e13077c52448c2b90a7705959a4fe458e7c29e7782d7ee6df1
4
+ data.tar.gz: 23d3530348907214d341704fa7b098728fa8b677422c8f595e12ae33c2b88ff3
5
5
  SHA512:
6
- metadata.gz: f9f8f54bca5c16e6f1068a9988374ab2a852febc8f4f8695fa7cf0e5bb21fed700bf5b7a34b0732693d55f520000528ed35719dc2488fdf3afb3b6224d2dce9d
7
- data.tar.gz: a883b80b556feaa34abc6544fe7b4c5f03d6da46597c6dc3170d1219ab66aa4018be3233f824ae6df551c50dc963974a35f72ea715037d596a26259bc8087d8a
6
+ metadata.gz: 6395fec9ab3701df1b77f32bbe950feff8b918b9f79374b5c107d9e282bb5e9e5fdd91c152dc46526fc201910e69b7c57e8ee968d11e222be3cfdd346c6c7ab8
7
+ data.tar.gz: a55e0b100034203af05855e8881fbfa161d30d6043b3831080ebff8d0f9ca658ca74fd11e1d30aef6887b3108493e4f690f0e08133ca4cf3bca36399cbcacdc4
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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
+
8
24
  ## [1.9.174.5] - 2026-09-15
9
25
 
10
26
  ### Changed
@@ -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).
@@ -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). */
@@ -1003,6 +1226,8 @@ static VALUE m_native_sentinel; /* module object = fallback marker */
1003
1226
  static VALUE materialize_xp_entry(VALUE document, VALUE cache,
1004
1227
  void *result, void *ptr, int kind,
1005
1228
  int idx);
1229
+ static VALUE binding_klass_for(int kind);
1230
+ static VALUE c_iteration_scope;
1006
1231
 
1007
1232
  static VALUE bulk_xpath_array(VALUE document, void *result);
1008
1233
 
@@ -1711,6 +1936,22 @@ void Init_native(void)
1711
1936
  nf_set_binding_text, 3);
1712
1937
  rb_define_module_function(m_native, "unlink_binding_node",
1713
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);
1714
1955
  rb_define_module_function(m_native, "fast_inner_xml",
1715
1956
  nf_fast_inner_xml, 1);
1716
1957
  rb_define_module_function(m_native, "doc_handle_attach",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.174.5"
4
+ VERSION = "1.9.174.6"
5
5
  end
@@ -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 = Leptris::XML::FFI.leptris_cdata_node_get_content(c_ptr)
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
@@ -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 = Leptris::XML::FFI.leptris_comment_node_get_content(c_ptr)
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
@@ -260,7 +260,9 @@ class Leptris::XML::Element < Leptris::XML::Node
260
260
  # Require the full Attr-hash face — a cold [] may have set the
261
261
  # version with only a partial @attr_values (ruby#150).
262
262
  return @attributes if @attributes && memo_hit?(@attributes_version)
263
- if native_fast?
263
+ # TODO.perf/28: the bulk faces touch no document state —
264
+ # scope-eligible via @addr_reads_fast.
265
+ if @addr_reads_fast
264
266
  # TODO.perf/10: both memo faces in one C walk.
265
267
  result, values = Leptris::XML::Native.bulk_attr_faces(
266
268
  @c_address, self)
@@ -492,6 +492,13 @@ class Leptris::XML::Node
492
492
  def visit(&block)
493
493
  return enum_for(:visit) unless block
494
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
495
502
  document = @document
496
503
  visitor = ::FFI::Function.new(
497
504
  :void, [:pointer, :pointer, :int, :int], blocking: true) do |_, node_ptr, entering, depth|
@@ -524,9 +531,18 @@ class Leptris::XML::Node
524
531
  # exception and returns non-zero, aborting the C walk — without
525
532
  # it the FFI dispatch silently swallowed the exception and the
526
533
  # walk continued with partially processed data.
527
- def traverse
528
- return enum_for(:traverse) unless block_given?
534
+ def traverse(&block)
535
+ return enum_for(:traverse) unless block
529
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
530
546
  error = nil
531
547
  self_address = @c_address
532
548
  callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _|
@@ -547,8 +563,12 @@ class Leptris::XML::Node
547
563
  def path
548
564
  return @path if memo_hit?(@path_version)
549
565
  ensure_alive!
550
- str_ptr = Leptris::XML::FFI.leptris_node_get_xpath(c_ptr)
551
- result = str_ptr.null? ? nil : Leptris::XML::FFI.read_owned_string(str_ptr)
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
552
572
  if @document
553
573
  @path = result
554
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 = Leptris::XML::FFI.leptris_pi_node_get_target(c_ptr)
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
- result = Leptris::XML::FFI.read_pi_data(
20
- Leptris::XML::FFI.leptris_pi_node_get_data(c_ptr)).to_s
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
@@ -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 = Leptris::XML::FFI.leptris_text_node_get_content(c_ptr)
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.5
4
+ version: 1.9.174.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -95,6 +95,8 @@ files:
95
95
  - TODO.perf/24-immutable-read-lanes.md
96
96
  - TODO.perf/25-scope-owned-bulk-path.md
97
97
  - TODO.perf/26-post-mutation-memo-seeding.md
98
+ - TODO.perf/27-c-yield-traversal.md
99
+ - TODO.perf/28-address-fills.md
98
100
  - TODO.restructure/01-constraint-compliance-audit.md
99
101
  - TODO.restructure/02-deep-copy-seam.md
100
102
  - TODO.restructure/03-evaluation-context-seam.md