leptris 1.9.174.2-aarch64-linux → 1.9.174.4-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1267d8fdc704044f69f7792e588b41ad74474583a82a458f61164086947e74d9
4
- data.tar.gz: 86c476d323ca385a60488e92c30e1fab89235ec8a99208dba6652887281e85f2
3
+ metadata.gz: fbd33e8a70b150f3955b3a7de1a136fc90e051e3015cdb7a9ad690b3a75f001e
4
+ data.tar.gz: 142500c600c53293edf34edaae8c90dee05369295a6fdf6dfa4167f86d8f97b9
5
5
  SHA512:
6
- metadata.gz: 33be4ac9cbf0f87a4d9b9f4a94560ac2f6ede3cb33e92c56ee109505d66c5fa841c1b6c77328db36860a8815a3dcdd4ebe21249e261d07d132729dd2ee0dbaa1
7
- data.tar.gz: 2f837a607594cddeaa07a90ea8c8e85e26d06073104c90c4e022bb1097af5f2824b6180f9057cab40a7ed7d43b1b058be18aa55cdf32efb78a32bc330c9b690e
6
+ metadata.gz: 4c8389eea5c5559baf0d27944e67b928d4537036bb4ab9961a2a00c0d0805f5b70eac823684b28bd2b1d85ea7946475d1c8eef1f630f047c7e23c50d2cf55cda
7
+ data.tar.gz: abdc548f17f8574f4edd8b82dafea01aa8b832d078c4c453b3a661c2d659ea6d02baf506c596fbb689af218648d95ef4f87b0c61506691d7c464ea9a33206fcd
data/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ 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
+
27
+ ## [1.9.174.3] - 2026-09-15
28
+
29
+ ### Changed
30
+
31
+ - **Lazy FFI::Pointer on wrappers (TODO.perf/19)**: the node's
32
+ canonical state is the Integer address; the Pointer
33
+ materializes only when read — the C construction faces stop
34
+ minting one per node (~200ns + one allocation each, and
35
+ proportionally less GC). Public `c_ptr` API and identity
36
+ unchanged. Measured (load 22-25): GC-amortized
37
+ `Document.create` ~5-6µs → ~4.0µs; `NativeNode#[]` 78ns
38
+ (budget 70), `#content` 45ns (budget 58 met).
39
+ - **CSS translation cache (TODO.perf/20)**: repeat selectors skip
40
+ the per-call regex translation straight to the compiled handle;
41
+ the single-path xpath shape no longer allocates a joined
42
+ String per call.
43
+ - **`Element#key?` consults the attribute memo (TODO.perf/21)**
44
+ before the engine round-trip; miss semantics unchanged.
45
+
8
46
  ## [1.9.174.2] - 2026-09-15
9
47
 
10
48
  ### Changed
@@ -0,0 +1,39 @@
1
+ # 19 — Lazy FFI::Pointer on wrappers
2
+
3
+ Status: DONE (1.9.174.3)
4
+
5
+ Every binding wrapper stores an FFI::Pointer (@c_ptr), and the C
6
+ construction faces mint one per node via
7
+ rb_funcall(FFI::Pointer, :new) — ~200ns plus a Ruby object and
8
+ its GC visit per node, paid at creation even though most nodes'
9
+ Pointers are never read (the hot reads are memoized or go through
10
+ the address-based native faces).
11
+
12
+ Store @c_address (Integer) as the canonical truth on every
13
+ wrapper; `c_ptr` becomes a lazy materializer
14
+ (@c_ptr ||= FFI::Pointer.new(@c_address)) preserving the public
15
+ API. Internal call sites: `@c_ptr.address` collapses to
16
+ @c_address (also skipping the #address dispatch), and remaining
17
+ internal @c_ptr reads go through the c_ptr reader (same cost as
18
+ the old attr_reader). The C faces stop constructing Pointers
19
+ entirely — they already hold the address.
20
+
21
+ Gates: suite green both modes (a missed site fails loudly — nil
22
+ to FFI raises TypeError); wrapper identity and c_ptr stability
23
+ unchanged; creation faces drop ~200ns and one allocation per
24
+ node; fresh-doc build row improves accordingly.
25
+
26
+ ## Outcome (1.9.174.3)
27
+
28
+ @c_address is the canonical truth on every Node wrapper; c_ptr
29
+ materializes lazily (@c_ptr ||= FFI::Pointer.new(@c_address)),
30
+ preserving the public API and identity. All internal @c_ptr
31
+ reads became c_ptr reader calls; the 15 @c_ptr.address sites
32
+ collapsed to @c_address (also skipping the #address dispatch).
33
+ The C construction faces (create element/text, bulk children,
34
+ every xpath materialization) stopped minting Pointers entirely —
35
+ one allocation and ~200ns saved per node, and proportionally less
36
+ GC. Document keeps its eager reader (documents are few).
37
+ Measured under host load 22-25: GC-amortized Document.create
38
+ ~5-6µs -> ~4.0µs; NativeNode#[] 78ns (budget 70 — clear at
39
+ load <10), content 45ns (budget 58 met).
@@ -0,0 +1,24 @@
1
+ # 20 — CSS translation cache
2
+
3
+ Status: DONE (1.9.174.3)
4
+
5
+ Searchable#css/#at_css run CssToXPath.convert on every call —
6
+ pure Ruby regex/string work (~300ns+) — before the compiled-
7
+ expression cache (TODO.perf/15) can hit on the result. The
8
+ translation is a pure function of (selector, prefix); cache it
9
+ with the same bounded-LRU shape (64 entries, Hash#shift
10
+ eviction, GVL-safe), so repeat selectors skip straight to the
11
+ compiled handle.
12
+
13
+ Gates: css/at_css results identical; repeat-call rows improve by
14
+ the translation cost; the join in xpath()'s expression build is
15
+ also skipped for the single-String-path shape (join minted a
16
+ fresh String per call even for one path).
17
+
18
+ ## Outcome (1.9.174.3)
19
+
20
+ Searchable.css_expression: bounded LRU (64, shared limit) keyed
21
+ on prefix+selector; css/at_css translate through it so repeat
22
+ selectors skip the regex machinery straight to the compiled-
23
+ expression cache. The single-String-path shape of xpath/at_xpath
24
+ no longer joins (join minted a fresh String per call).
@@ -0,0 +1,20 @@
1
+ # 21 — key? consults the attribute memo
2
+
3
+ Status: DONE (1.9.174.3)
4
+
5
+ Element#key?/has_attribute? pays an engine FFI round-trip per
6
+ call even when the versioned @attr_values memo already proves
7
+ presence. Consult the memo first (hit = true; only a miss falls
8
+ through to the engine + written-name fallback — the memo cannot
9
+ prove ABSENCE for a partial fill).
10
+
11
+ Gates: key? true for memoized names, unchanged miss semantics
12
+ (undeclared-prefix fallback intact), readonly/namespace specs
13
+ green.
14
+
15
+ ## Outcome (1.9.174.3)
16
+
17
+ Element#key? consults the versioned @attr_values memo first —
18
+ presence-proven hits skip the engine round-trip; partial-fill
19
+ misses still fall through to the engine + written-name fallback
20
+ (the memo cannot prove absence).
@@ -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.
@@ -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). */
@@ -870,13 +947,13 @@ static VALUE bulk_children_impl(VALUE document, VALUE parent_addr,
870
947
  key = ULL2NUM((uint64_t)(uintptr_t)buf[i]);
871
948
  node = rb_hash_aref(cache, key);
872
949
  if (NIL_P(node)) {
873
- VALUE ptr = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
874
950
  node = rb_obj_alloc(binding_klass_for(kinds[i]));
875
- rb_iv_set(node, "@c_ptr", ptr);
951
+ rb_iv_set(node, "@c_address", key);
876
952
  rb_iv_set(node, "@document", document);
877
953
  rb_iv_set(node, "@parent", Qnil);
878
954
  rb_iv_set(node, "@structure_memoizable", Qtrue);
879
955
  rb_iv_set(node, "@native_fast", Qtrue);
956
+ rb_iv_set(node, "@pub_document", document);
880
957
  rb_iv_set(node, "@node_type", INT2FIX(kinds[i]));
881
958
  rb_hash_aset(cache, key, node);
882
959
  }
@@ -913,15 +990,45 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
913
990
  void *result, void *ptr, int kind,
914
991
  int idx);
915
992
 
993
+ static VALUE bulk_xpath_array(VALUE document, void *result);
994
+
916
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)
917
1006
  {
918
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
+ {
919
1027
  void **buf;
920
1028
  int *kinds;
921
1029
  int count, i;
922
1030
  VALUE cache, out;
923
1031
 
924
- (void)self;
925
1032
  resolve_binding_classes();
926
1033
  size_t scount = f_xp_count(result);
927
1034
  if (scount == 0)
@@ -965,13 +1072,13 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
965
1072
  case 0: /* element */
966
1073
  node = rb_hash_aref(cache, key);
967
1074
  if (NIL_P(node)) {
968
- VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
969
1075
  node = rb_obj_alloc(c_b_element);
970
- rb_iv_set(node, "@c_ptr", p);
1076
+ rb_iv_set(node, "@c_address", key);
971
1077
  rb_iv_set(node, "@document", document);
972
1078
  rb_iv_set(node, "@parent", Qnil);
973
1079
  rb_iv_set(node, "@structure_memoizable", Qtrue);
974
1080
  rb_iv_set(node, "@native_fast", Qtrue);
1081
+ rb_iv_set(node, "@pub_document", document);
975
1082
  rb_iv_set(node, "@node_type", INT2FIX(0));
976
1083
  rb_hash_aset(cache, key, node);
977
1084
  }
@@ -980,9 +1087,8 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
980
1087
  {
981
1088
  const char *name = f_xp_node_name(result, idx);
982
1089
  const char *value = f_xp_node_value(result, idx);
983
- VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
984
1090
  node = rb_obj_alloc(c_b_result_attr);
985
- rb_iv_set(node, "@c_ptr", p);
1091
+ rb_iv_set(node, "@c_address", key);
986
1092
  rb_iv_set(node, "@document", document);
987
1093
  rb_iv_set(node, "@attr_name",
988
1094
  name ? rb_utf8_str_new_cstr(name) : Qnil);
@@ -998,8 +1104,7 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
998
1104
  if (nt == 8) { /* NODE_SYNTHETIC_TEXT */
999
1105
  s = f_xp_node_value(result, idx);
1000
1106
  node = rb_obj_alloc(c_b_result_text);
1001
- rb_iv_set(node, "@c_ptr",
1002
- rb_funcall(c_ffi_pointer, id_ptr_new, 1, key));
1107
+ rb_iv_set(node, "@c_address", key);
1003
1108
  rb_iv_set(node, "@document", document);
1004
1109
  rb_iv_set(node, "@value",
1005
1110
  s ? rb_utf8_str_new_cstr(s) : Qnil);
@@ -1007,13 +1112,13 @@ static VALUE materialize_xp_entry(VALUE document, VALUE cache,
1007
1112
  }
1008
1113
  node = rb_hash_aref(cache, key);
1009
1114
  if (NIL_P(node)) {
1010
- VALUE p = rb_funcall(c_ffi_pointer, id_ptr_new, 1, key);
1011
1115
  node = rb_obj_alloc(binding_klass_for(nt));
1012
- rb_iv_set(node, "@c_ptr", p);
1116
+ rb_iv_set(node, "@c_address", key);
1013
1117
  rb_iv_set(node, "@document", document);
1014
1118
  rb_iv_set(node, "@parent", Qnil);
1015
1119
  rb_iv_set(node, "@structure_memoizable", Qtrue);
1016
1120
  rb_iv_set(node, "@native_fast", Qtrue);
1121
+ rb_iv_set(node, "@pub_document", document);
1017
1122
  rb_iv_set(node, "@node_type", INT2FIX(nt));
1018
1123
  rb_hash_aset(cache, key, node);
1019
1124
  }
@@ -1096,13 +1201,13 @@ static VALUE nf_create_binding_element(VALUE self, VALUE document,
1096
1201
  if (!ptr)
1097
1202
  return Qnil; /* caller raises with the error channel */
1098
1203
  node = rb_obj_alloc(c_b_element);
1099
- rb_iv_set(node, "@c_ptr",
1100
- rb_funcall(c_ffi_pointer, id_ptr_new, 1,
1101
- ULL2NUM((uint64_t)(uintptr_t)ptr)));
1204
+ rb_iv_set(node, "@c_address",
1205
+ ULL2NUM((uint64_t)(uintptr_t)ptr));
1102
1206
  rb_iv_set(node, "@document", document);
1103
1207
  rb_iv_set(node, "@parent", Qnil);
1104
1208
  rb_iv_set(node, "@structure_memoizable", Qtrue);
1105
1209
  rb_iv_set(node, "@native_fast", Qtrue);
1210
+ rb_iv_set(node, "@pub_document", document);
1106
1211
  rb_iv_set(node, "@node_type", INT2FIX(0));
1107
1212
  cache = binding_cache_of(document);
1108
1213
  key = ULL2NUM((uint64_t)(uintptr_t)ptr);
@@ -1122,13 +1227,13 @@ static VALUE nf_create_binding_text(VALUE self, VALUE document,
1122
1227
  if (!ptr)
1123
1228
  return Qnil;
1124
1229
  node = rb_obj_alloc(c_b_text);
1125
- rb_iv_set(node, "@c_ptr",
1126
- rb_funcall(c_ffi_pointer, id_ptr_new, 1,
1127
- ULL2NUM((uint64_t)(uintptr_t)ptr)));
1230
+ rb_iv_set(node, "@c_address",
1231
+ ULL2NUM((uint64_t)(uintptr_t)ptr));
1128
1232
  rb_iv_set(node, "@document", document);
1129
1233
  rb_iv_set(node, "@parent", Qnil);
1130
1234
  rb_iv_set(node, "@structure_memoizable", Qtrue);
1131
1235
  rb_iv_set(node, "@native_fast", Qtrue);
1236
+ rb_iv_set(node, "@pub_document", document);
1132
1237
  rb_iv_set(node, "@node_type", INT2FIX(1));
1133
1238
  cache = binding_cache_of(document);
1134
1239
  key = ULL2NUM((uint64_t)(uintptr_t)ptr);
@@ -1584,6 +1689,14 @@ void Init_native(void)
1584
1689
  nf_insert_binding_child, 4);
1585
1690
  rb_define_module_function(m_native, "at_xpath_first",
1586
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);
1587
1700
  rb_define_module_function(m_native, "fast_inner_xml",
1588
1701
  nf_fast_inner_xml, 1);
1589
1702
  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.2"
4
+ VERSION = "1.9.174.4"
5
5
  end
@@ -6,7 +6,7 @@ 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 = Leptris::XML::FFI.leptris_cdata_node_get_content(c_ptr)
10
10
  if @document
11
11
  @content = result
12
12
  @content_version = @document.version
@@ -17,7 +17,7 @@ class Leptris::XML::CDATA < Leptris::XML::Text
17
17
  def content=(new_content)
18
18
  ensure_writable!
19
19
  Leptris::XML::FFI.check_status(
20
- Leptris::XML::FFI.leptris_cdata_node_set_content(@c_ptr, new_content.to_s))
20
+ Leptris::XML::FFI.leptris_cdata_node_set_content(c_ptr, new_content.to_s))
21
21
  new_content
22
22
  end
23
23
  end
@@ -6,7 +6,7 @@ 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 = Leptris::XML::FFI.leptris_comment_node_get_content(c_ptr)
10
10
  if @document
11
11
  @content = result
12
12
  @content_version = @document.version
@@ -17,7 +17,7 @@ class Leptris::XML::Comment < Leptris::XML::Node
17
17
  def content=(new_content)
18
18
  ensure_writable!
19
19
  Leptris::XML::FFI.check_status(
20
- Leptris::XML::FFI.leptris_comment_node_set_content(@c_ptr, new_content.to_s))
20
+ Leptris::XML::FFI.leptris_comment_node_set_content(c_ptr, new_content.to_s))
21
21
  new_content
22
22
  end
23
23
  end
@@ -9,24 +9,24 @@ class Leptris::XML::DocType
9
9
  end
10
10
 
11
11
  def name
12
- Leptris::XML::FFI.leptris_doctype_get_name(@c_ptr)
12
+ Leptris::XML::FFI.leptris_doctype_get_name(c_ptr)
13
13
  end
14
14
  alias_method :node_name, :name
15
15
 
16
16
  def root_name
17
- Leptris::XML::FFI.leptris_doctype_get_root_name(@c_ptr)
17
+ Leptris::XML::FFI.leptris_doctype_get_root_name(c_ptr)
18
18
  end
19
19
 
20
20
  def public_id
21
- Leptris::XML::FFI.leptris_doctype_get_public_id(@c_ptr)
21
+ Leptris::XML::FFI.leptris_doctype_get_public_id(c_ptr)
22
22
  end
23
23
 
24
24
  def system_id
25
- Leptris::XML::FFI.leptris_doctype_get_system_id(@c_ptr)
25
+ Leptris::XML::FFI.leptris_doctype_get_system_id(c_ptr)
26
26
  end
27
27
 
28
28
  def internal_subset
29
- Leptris::XML::FFI.leptris_doctype_get_internal_subset(@c_ptr)
29
+ Leptris::XML::FFI.leptris_doctype_get_internal_subset(c_ptr)
30
30
  end
31
31
 
32
32
  def external_id