leptris 1.9.174.9 → 1.9.174.10

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: 73130c8bf549007d80bf610c22bc831df382f5fe972316835a6dc806885929c5
4
- data.tar.gz: 3b6bfb23c72a2ed3532eb64886c9f36603975b5d601aa693f6b3ba821bb19de2
3
+ metadata.gz: f0e347192431e6ed3ff15bed0fca55fb103ba7307a33d889014e72c3ece0dba8
4
+ data.tar.gz: f7edb28afa62063bbc1f0ab2cfcc349619a8944249f69f41b40e054e016b5b45
5
5
  SHA512:
6
- metadata.gz: 621d0f62f7869c1a4b0c63212dce8694f51cc1914a3f115551b7e91c1f5b2bc0af9a33dcb2baa4d462fc5a12f72225a228dee59f7e4a7f70ed5173e0afe6932b
7
- data.tar.gz: 2a389319493ace9979ea863853b75c56149f73dcc5e204c362bd8bdbaacd335d94a58bd9bb3d4ed83f6c83df3331b53549bb462fa19980fb996a8c2798c3fbdc
6
+ metadata.gz: 660309e07c7ce84a4a68bdca9c46a1970f0af6a1bd2e316acca600de60cf54cbd445cd0814232593dd1752f59965d7df8ede74c14da977005b48cbec4ec276fd
7
+ data.tar.gz: ffe8c0e4f6fc98f4bda50187b3c740e278aae6c82ec384f1ac6843673276b916910a1ab72a4c740a69e0006992418c83eb92280a619b18fac69c8568f5b7639e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ 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.10] - 2026-09-16
9
+
10
+ ### Changed
11
+
12
+ - **Status raises in C (TODO.perf/36)**: every C mutation face
13
+ raises `Leptris::XML::Error` with the exact `status_message`
14
+ format — the Ruby `check_status` dispatch leaves the fast
15
+ mutation paths (and the success/lift-marker collision it
16
+ briefly introduced — Qtrue on success now — was caught by the
17
+ bottom-up construction spec). `to_xml(encoding:)` rides the ext
18
+ serializer face (encoding VALUE, nil → NULL).
19
+ - **Document lazy Pointer (TODO.perf/37)**: `#c_ptr` materializes
20
+ once over `@c_address` (freed docs answer nil); the parse/create
21
+ faces skip the Pointer mint entirely. The ffi gem rejects
22
+ Integers for typedef'd pointer params — the search paths keep
23
+ the lazy Pointer.
24
+ - **Atomic bundle vendoring**: replacing a mapped `native.bundle`
25
+ in place gets the next loader SIGKILLed (CODESIGNING Invalid
26
+ Page) — the Rakefile now cp+mv's atomically.
27
+
8
28
  ## [1.9.174.9] - 2026-09-16
9
29
 
10
30
  ### Changed
data/Rakefile CHANGED
@@ -172,7 +172,12 @@ task :compile do
172
172
  # Build-log proof of the linkage contract: libSystem only.
173
173
  puts `otool -L #{bundle}`
174
174
  end
175
- cp(bundle, "lib/leptris/xml/#{File.basename(bundle)}")
175
+ # Atomic replace: overwriting a bundle another process still
176
+ # has mapped gets that loader SIGKILLed (CODESIGNING Invalid
177
+ # Page) — cp to a temp name, then mv.
178
+ dest = "lib/leptris/xml/#{File.basename(bundle)}"
179
+ cp(bundle, "#{dest}.new")
180
+ mv("#{dest}.new", dest)
176
181
  puts "Vendored native layer (#{File.basename(bundle)}) into lib/leptris/xml/"
177
182
  end
178
183
  end
@@ -0,0 +1,34 @@
1
+ # 36 — Status raises in C + encoding on the serializer faces
2
+
3
+ Status: DONE (1.9.174.10)
4
+
5
+ Every C mutation face returns INT2FIX(st) for a Ruby check_status
6
+ dispatch (~30ns per mutation, plus a whole class of
7
+ "forgot-to-check" bug the compiler cannot catch). With
8
+ leptris_status_string and leptris_last_error both dlsym'd, the
9
+ faces raise Leptris::XML::Error in C with the exact
10
+ status_message format ("base" or "base (detail)") — call sites
11
+ shrink to the bare face call.
12
+
13
+ The ext serializer faces skip the encoding-tagged shape entirely
14
+ (to_xml(encoding: ...) still builds the options struct through
15
+ FFI): the faces gain an encoding VALUE (nil -> NULL, the Ruby
16
+ string's bytes stay valid across the synchronous engine call).
17
+
18
+ Gates: every mutation's failure message byte-identical to
19
+ check_status; encoding-tagged to_xml output identical to the FFI
20
+ path (declaration, no anchor lifetime issues); suite both modes.
21
+
22
+ ## Outcome (1.9.174.10)
23
+
24
+ All seven mutation faces raise Leptris::XML::Error in C with the
25
+ exact status_message format (status_string + optional
26
+ " (last_error)") — the Ruby check_status dispatch disappears from
27
+ every fast mutation. TRAP FOUND: the faces' success return became
28
+ Qnil, colliding with the lift-needed marker — every fast mutation
29
+ fell through to the slow path and re-applied (order-corrupting
30
+ for inserts; the v180 bottom-up spec caught it). Success now
31
+ returns Qtrue. The serializer faces take an encoding VALUE (nil
32
+ -> NULL; the Ruby string's bytes live across the synchronous
33
+ call), and to_xml(encoding:) rides the ext face instead of the
34
+ options-struct FFI path.
@@ -0,0 +1,28 @@
1
+ # 37 — Document lazy Pointer + address-based xpath contexts
2
+
3
+ Status: DONE (1.9.174.10)
4
+
5
+ Documents still mint an FFI::Pointer eagerly (the parse/create
6
+ faces pay the funcall + one allocation + its GC visit per
7
+ document). Make #c_ptr lazy like Node's (attr_reader becomes a
8
+ materializer over @c_address), skip the Pointer in
9
+ build_binding_document, and feed the search paths Integer
10
+ addresses (the ffi gem converts Integers for :pointer params) so
11
+ per-xpath document Pointers never materialize at all.
12
+
13
+ Gates: c_ptr stability and public API unchanged; every FFI
14
+ document consumer still works (options paths, c14n, fragment
15
+ fallback); suite both modes.
16
+
17
+ ## Outcome (1.9.174.10)
18
+
19
+ Document#c_ptr materializes lazily over @c_address (freed docs
20
+ answer nil — never re-materialize); build_binding_document skips
21
+ the Pointer funcall (one alloc + ~200ns per document). TRAP
22
+ FOUND: this ffi version does NOT convert Integers for typedef'd
23
+ pointer params — passing c_address into the compiled-eval calls
24
+ failed with ":pointer argument is not a valid pointer"; the
25
+ search paths keep passing the (now lazy, once-per-document)
26
+ Pointer. Also: replacing a mapped native.bundle in place gets
27
+ the next loader SIGKILLed (CODESIGNING Invalid Page) — the
28
+ vendoring cp is now an atomic cp+mv.
@@ -97,6 +97,7 @@ typedef void *(*elem_copy_fn)(void *, void *);
97
97
  typedef void *(*parse_str_fn)(const char *, size_t, void *);
98
98
  typedef void *(*parse_frag_fn)(const char *, size_t, void *, int *);
99
99
  typedef const char *(*last_err_fn)(void);
100
+ typedef const char *(*status_str_fn)(int);
100
101
  static set_str_fn f_elem_set_name, f_elem_set_text, f_text_set_content;
101
102
  static node_unlink_fn f_node_unlink;
102
103
  static traverse_fn f_node_traverse;
@@ -107,6 +108,7 @@ static elem_copy_fn f_elem_copy;
107
108
  static parse_str_fn f_parse_str;
108
109
  static parse_frag_fn f_parse_frag;
109
110
  static last_err_fn f_last_err;
111
+ static status_str_fn f_status_str;
110
112
  static set_root_fn f_set_root;
111
113
  static doc_free_fn f_doc_free;
112
114
 
@@ -125,8 +127,10 @@ static void resolve_binding_classes(void);
125
127
  static VALUE binding_cache_of(VALUE document);
126
128
  static VALUE binding_klass_for(int kind);
127
129
  static VALUE c_iteration_scope;
130
+ static VALUE c_leptris_error;
128
131
  static VALUE c_b_document, c_b_freed, c_ffi_pointer;
129
132
  static ID id_ptr_new, id_freed_new, id_alive;
133
+ static void check_status_c(int st);
130
134
 
131
135
  #define NT_ELEMENT 0
132
136
 
@@ -230,6 +234,7 @@ static void resolve_symbols(const char *lib_path)
230
234
  f_parse_str = (parse_str_fn)lib_sym(h, "leptris_parse_string");
231
235
  f_parse_frag = (parse_frag_fn)lib_sym(h, "leptris_parse_fragment");
232
236
  f_last_err = (last_err_fn)lib_sym(h, "leptris_last_error");
237
+ f_status_str = (status_str_fn)lib_sym(h, "leptris_status_string");
233
238
  f_set_root = (set_root_fn)lib_sym(h, "leptris_document_set_root");
234
239
  f_doc_free = (doc_free_fn)lib_sym(h, "leptris_document_free");
235
240
  if (!f_elem_name || !f_text_content || !f_attr ||
@@ -243,7 +248,7 @@ static void resolve_symbols(const char *lib_path)
243
248
  !f_text_set_content || !f_node_unlink ||
244
249
  !f_node_traverse || !f_node_visit || !f_free_str ||
245
250
  !f_node_xpath || !f_elem_copy || !f_parse_str ||
246
- !f_parse_frag || !f_last_err ||
251
+ !f_parse_frag || !f_last_err || !f_status_str ||
247
252
  !f_doc_free ||
248
253
  !f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
249
254
  !f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
@@ -640,7 +645,8 @@ static VALUE nf_append_binding_child(VALUE self, VALUE document,
640
645
  rb_ivar_set(document, id_iv_version,
641
646
  LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
642
647
  st = f_append_child(parent, child);
643
- return INT2FIX(st);
648
+ check_status_c(st);
649
+ return Qtrue;
644
650
  }
645
651
 
646
652
  /* C-bound set_attribute (TODO.perf/11, #204 gap row 0.25x): the
@@ -669,7 +675,8 @@ static VALUE nf_set_binding_attribute(VALUE self, VALUE document,
669
675
  * embedded-NUL value raises rather than truncates. */
670
676
  st = f_set_attr(node, RSTRING_PTR(StringValue(name)),
671
677
  StringValueCStr(value));
672
- return INT2FIX(st);
678
+ check_status_c(st);
679
+ return Qtrue;
673
680
  }
674
681
 
675
682
  /* C-bound value mutations (TODO.perf/23): the same gates + bump
@@ -692,7 +699,8 @@ static VALUE nf_set_binding_name(VALUE self, VALUE document,
692
699
  rb_ivar_set(document, id_iv_version,
693
700
  LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
694
701
  st = f_elem_set_name(node, StringValueCStr(name));
695
- return INT2FIX(st);
702
+ check_status_c(st);
703
+ return Qtrue;
696
704
  }
697
705
 
698
706
  /* Element text or text-node content — kind-dispatched in C. */
@@ -716,7 +724,8 @@ static VALUE nf_set_binding_text(VALUE self, VALUE document,
716
724
  st = f_node_type(node) == NT_ELEMENT
717
725
  ? f_elem_set_text(node, StringValueCStr(content))
718
726
  : f_text_set_content(node, StringValueCStr(content));
719
- return INT2FIX(st);
727
+ check_status_c(st);
728
+ return Qtrue;
720
729
  }
721
730
 
722
731
  static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
@@ -737,7 +746,8 @@ static VALUE nf_unlink_binding_node(VALUE self, VALUE document,
737
746
  rb_ivar_set(document, id_iv_version,
738
747
  LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
739
748
  st = f_node_unlink(node);
740
- return INT2FIX(st);
749
+ check_status_c(st);
750
+ return Qtrue;
741
751
  }
742
752
 
743
753
  /* C-bound root= (TODO.perf/33): gates + bump + engine set_root
@@ -761,7 +771,8 @@ static VALUE nf_set_binding_root(VALUE self, VALUE document,
761
771
  rb_ivar_set(document, id_iv_version,
762
772
  LONG2FIX(FIX2LONG(rb_ivar_get(document, id_iv_version)) + 1));
763
773
  st = f_set_root(doc_ptr_of(document), node);
764
- return INT2FIX(st);
774
+ check_status_c(st);
775
+ return Qtrue;
765
776
  }
766
777
 
767
778
  /* ---- C-yield traversal (TODO.perf/27) ----------------------------
@@ -1074,7 +1085,8 @@ static VALUE nf_insert_binding_child(VALUE self, VALUE document,
1074
1085
  rb_raise(rb_eArgError, "invalid insert mode %d", m);
1075
1086
  return Qnil;
1076
1087
  }
1077
- return INT2FIX(st);
1088
+ check_status_c(st);
1089
+ return Qtrue;
1078
1090
  }
1079
1091
 
1080
1092
  /* Builder factories (#149): create in C, wrap as NativeNode — no
@@ -1225,6 +1237,7 @@ static void resolve_binding_classes(void)
1225
1237
  c_b_document = rb_path2class("Leptris::XML::Document");
1226
1238
  c_b_freed = rb_path2class("Leptris::XML::Document::Freed");
1227
1239
  c_iteration_scope = rb_path2class("Leptris::XML::IterationScope");
1240
+ c_leptris_error = rb_path2class("Leptris::XML::Error");
1228
1241
  id_ptr_new = rb_intern("new");
1229
1242
  rb_gc_register_mark_object(c_b_element);
1230
1243
  rb_gc_register_mark_object(c_b_text);
@@ -1239,6 +1252,7 @@ static void resolve_binding_classes(void)
1239
1252
  rb_gc_register_mark_object(c_b_document);
1240
1253
  rb_gc_register_mark_object(c_b_freed);
1241
1254
  rb_gc_register_mark_object(c_iteration_scope);
1255
+ rb_gc_register_mark_object(c_leptris_error);
1242
1256
  }
1243
1257
 
1244
1258
  static VALUE binding_klass_for(int kind)
@@ -1635,7 +1649,8 @@ static char *ser_buf;
1635
1649
  static size_t ser_cap;
1636
1650
 
1637
1651
  static VALUE fast_serialize(serialize_into_fn fn, void *node,
1638
- int indent, int xml_declaration)
1652
+ int indent, int xml_declaration,
1653
+ VALUE encoding)
1639
1654
  {
1640
1655
  struct serialize_opts opts;
1641
1656
  char stack_buf[4096];
@@ -1646,7 +1661,11 @@ static VALUE fast_serialize(serialize_into_fn fn, void *node,
1646
1661
 
1647
1662
  opts.indent = indent;
1648
1663
  opts.xml_declaration = xml_declaration;
1649
- opts.encoding = NULL;
1664
+ /* The Ruby string's bytes stay valid across the synchronous
1665
+ * engine call (TODO.perf/36). */
1666
+ opts.encoding = NIL_P(encoding)
1667
+ ? NULL
1668
+ : StringValueCStr(encoding);
1650
1669
  /* Probe the largest buffer we own: a big scratch from a prior
1651
1670
  * call usually fits (single serialization); cold calls start
1652
1671
  * on the stack buffer. */
@@ -1672,21 +1691,25 @@ static VALUE fast_serialize(serialize_into_fn fn, void *node,
1672
1691
  }
1673
1692
 
1674
1693
  static VALUE nf_fast_document_xml(VALUE self, VALUE addr,
1675
- VALUE indent, VALUE decl)
1694
+ VALUE indent, VALUE decl,
1695
+ VALUE encoding)
1676
1696
  {
1677
1697
  (void)self;
1678
1698
  return fast_serialize(f_doc_serialize,
1679
1699
  (void *)(uintptr_t)NUM2ULL(addr),
1680
- NUM2INT(indent), RTEST(decl) ? 1 : 0);
1700
+ NUM2INT(indent), RTEST(decl) ? 1 : 0,
1701
+ encoding);
1681
1702
  }
1682
1703
 
1683
1704
  static VALUE nf_fast_element_xml(VALUE self, VALUE addr,
1684
- VALUE indent, VALUE decl)
1705
+ VALUE indent, VALUE decl,
1706
+ VALUE encoding)
1685
1707
  {
1686
1708
  (void)self;
1687
1709
  return fast_serialize(f_elem_serialize,
1688
1710
  (void *)(uintptr_t)NUM2ULL(addr),
1689
- NUM2INT(indent), RTEST(decl) ? 1 : 0);
1711
+ NUM2INT(indent), RTEST(decl) ? 1 : 0,
1712
+ encoding);
1690
1713
  }
1691
1714
 
1692
1715
  /* ---- inner_html in one C pass (TODO.perf/18) --------------------
@@ -1861,8 +1884,6 @@ static VALUE nf_copy_binding_element(VALUE self, VALUE document,
1861
1884
  return Qnil;
1862
1885
  doc_addr = ULL2NUM((uint64_t)(uintptr_t)doc);
1863
1886
  new_doc = rb_obj_alloc(c_b_document);
1864
- rb_iv_set(new_doc, "@c_ptr",
1865
- rb_funcall(c_ffi_pointer, id_ptr_new, 1, doc_addr));
1866
1887
  rb_iv_set(new_doc, "@c_address", doc_addr);
1867
1888
  freed = rb_funcall(c_b_freed, id_freed_new, 1, ID2SYM(id_alive));
1868
1889
  rb_iv_set(new_doc, "@freed", freed);
@@ -1909,6 +1930,23 @@ static VALUE build_binding_document(void *doc)
1909
1930
  return new_doc;
1910
1931
  }
1911
1932
 
1933
+ /* check_status in C (TODO.perf/36): the exact status_message
1934
+ * format — "base" or "base (detail)" — so the mutation faces
1935
+ * raise instead of returning codes for a Ruby dispatch. */
1936
+ static void check_status_c(int st)
1937
+ {
1938
+ const char *base, *detail;
1939
+
1940
+ if (st == 0)
1941
+ return;
1942
+ base = f_status_str(st);
1943
+ detail = f_last_err();
1944
+ if (detail && *detail)
1945
+ rb_raise(c_leptris_error, "%s (%s)",
1946
+ base ? base : "?", detail);
1947
+ rb_raise(c_leptris_error, "%s", base ? base : "?");
1948
+ }
1949
+
1912
1950
  /* Document.parse default path in one dispatch (TODO.perf/35):
1913
1951
  * leptris_parse_string + wrapper + handle. Qnil = parse failure
1914
1952
  * (the caller raises with the same message shape). */
@@ -1974,7 +2012,7 @@ static VALUE nf_append_markup(VALUE self, VALUE document,
1974
2012
  next_child = f_next_sibling(child);
1975
2013
  status = f_append_child(parent, child);
1976
2014
  if (status != 0)
1977
- return INT2FIX(-1000 - status);
2015
+ check_status_c(status);
1978
2016
  count++;
1979
2017
  }
1980
2018
  return INT2FIX(count);
@@ -2231,7 +2269,7 @@ void Init_native(void)
2231
2269
  nf_bulk_element_children, 2);
2232
2270
  rb_define_module_function(m_native, "bulk_xpath", nf_bulk_xpath, 2);
2233
2271
  rb_define_module_function(m_native, "fast_document_xml",
2234
- nf_fast_document_xml, 3);
2272
+ nf_fast_document_xml, 4);
2235
2273
  rb_define_module_function(m_native, "fast_element_xml",
2236
- nf_fast_element_xml, 3);
2274
+ nf_fast_element_xml, 4);
2237
2275
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.174.9"
4
+ VERSION = "1.9.174.10"
5
5
  end
@@ -3,7 +3,6 @@
3
3
  require "ffi"
4
4
 
5
5
  class Leptris::XML::Document
6
- attr_reader :c_ptr
7
6
 
8
7
  # @api private
9
8
  # Internal flag container shared between the Document instance and its
@@ -57,6 +56,19 @@ class Leptris::XML::Document
57
56
  # document for trees that are freed before any wrap.
58
57
  end
59
58
 
59
+ # TODO.perf/37: the Integer address is canonical; the Pointer
60
+ # materializes only when read (parse/create faces skip it).
61
+ # A freed document answers nil — never re-materialize a stale
62
+ # or absent address.
63
+ def c_ptr
64
+ @c_ptr ||= (@c_address && ::FFI::Pointer.new(@c_address))
65
+ end
66
+
67
+ # The Integer twin of #c_ptr.
68
+ def c_address
69
+ @c_address
70
+ end
71
+
60
72
  def wrapper_cache
61
73
  @wrapper_cache ||= {}
62
74
  end
@@ -308,8 +320,7 @@ class Leptris::XML::Document
308
320
  # dispatch (the FFI call plus the c_ptr materialization fed
309
321
  # ~5% of a fresh-doc build).
310
322
  if defined?(Leptris::XML::NATIVE_FAST)
311
- Leptris::XML::FFI.check_status(
312
- Leptris::XML::Native.set_binding_root(self, element.c_address))
323
+ Leptris::XML::Native.set_binding_root(self, element.c_address)
313
324
  else
314
325
  Leptris::XML::FFI.check_status(
315
326
  Leptris::XML::FFI.leptris_document_set_root(c_ptr, element.c_ptr))
@@ -25,9 +25,8 @@ class Leptris::XML::Element < Leptris::XML::Node
25
25
 
26
26
  def name=(new_name)
27
27
  if @native_fast
28
- Leptris::XML::FFI.check_status(
29
- Leptris::XML::Native.set_binding_name(
30
- @document, @c_address, new_name))
28
+ Leptris::XML::Native.set_binding_name(
29
+ @document, @c_address, new_name)
31
30
  return @name = new_name
32
31
  end
33
32
  ensure_writable!
@@ -54,9 +53,8 @@ class Leptris::XML::Element < Leptris::XML::Node
54
53
 
55
54
  def content=(new_content)
56
55
  if @native_fast
57
- Leptris::XML::FFI.check_status(
58
- Leptris::XML::Native.set_binding_text(
59
- @document, @c_address, new_content.to_s))
56
+ Leptris::XML::Native.set_binding_text(
57
+ @document, @c_address, new_content.to_s)
60
58
  @content = new_content.to_s
61
59
  @content_version = @document.version if @document
62
60
  return new_content
@@ -138,9 +136,8 @@ class Leptris::XML::Element < Leptris::XML::Node
138
136
  # set in one dispatch; the bump drops the version-stamped
139
137
  # attribute memos on both surfaces.
140
138
  if native_fast_children?
141
- Leptris::XML::FFI.check_status(
142
- Leptris::XML::Native.set_binding_attribute(
143
- @document, @c_address, key.to_s, value.to_s))
139
+ Leptris::XML::Native.set_binding_attribute(
140
+ @document, @c_address, key.to_s, value.to_s)
144
141
  seed_attribute_memo(key.to_s, value.to_s)
145
142
  return value
146
143
  end
@@ -374,10 +371,8 @@ class Leptris::XML::Element < Leptris::XML::Node
374
371
  # bump + engine insert in one dispatch; Qnil = the child needs
375
372
  # the namespace lift — the full path below handles it.
376
373
  if native_fast_children?
377
- st = Leptris::XML::Native.insert_binding_child(
378
- @document, @c_address, node.c_ptr.address, 1)
379
- unless st.nil?
380
- Leptris::XML::FFI.check_status(st)
374
+ unless Leptris::XML::Native.insert_binding_child(
375
+ @document, @c_address, node.c_ptr.address, 1).nil?
381
376
  Leptris::XML::Node.invalidate_cross_document!(node, @document)
382
377
  return node
383
378
  end
@@ -397,10 +392,8 @@ class Leptris::XML::Element < Leptris::XML::Node
397
392
  # bump + engine insert in one dispatch; Qnil = the child needs
398
393
  # the namespace lift — the full path below handles it.
399
394
  if native_fast_children?
400
- st = Leptris::XML::Native.insert_binding_child(
401
- @document, @c_address, node.c_ptr.address, 2)
402
- unless st.nil?
403
- Leptris::XML::FFI.check_status(st)
395
+ unless Leptris::XML::Native.insert_binding_child(
396
+ @document, @c_address, node.c_ptr.address, 2).nil?
404
397
  Leptris::XML::Node.invalidate_cross_document!(node, @document)
405
398
  return node
406
399
  end
@@ -420,10 +413,8 @@ class Leptris::XML::Element < Leptris::XML::Node
420
413
  # bump + engine insert in one dispatch; Qnil = the child needs
421
414
  # the namespace lift — the full path below handles it.
422
415
  if native_fast_children?
423
- st = Leptris::XML::Native.insert_binding_child(
424
- @document, @c_address, node.c_ptr.address, 3)
425
- unless st.nil?
426
- Leptris::XML::FFI.check_status(st)
416
+ unless Leptris::XML::Native.insert_binding_child(
417
+ @document, @c_address, node.c_ptr.address, 3).nil?
427
418
  Leptris::XML::Node.invalidate_cross_document!(node, @document)
428
419
  return node
429
420
  end
@@ -539,10 +530,10 @@ class Leptris::XML::Element < Leptris::XML::Node
539
530
  # means the child needs the namespace lift — fall through
540
531
  # to the full path.
541
532
  if native_fast_children?
542
- st = Leptris::XML::Native.append_binding_child(
543
- @document, @c_address, node_or_markup.c_ptr.address)
544
- unless st.nil?
545
- Leptris::XML::FFI.check_status(st)
533
+ # The face raises on failure (TODO.perf/36); Qnil = the
534
+ # child needs the lift — the full path handles it.
535
+ unless Leptris::XML::Native.append_binding_child(
536
+ @document, @c_address, node_or_markup.c_ptr.address).nil?
546
537
  Leptris::XML::Node.invalidate_cross_document!(node_or_markup, @document)
547
538
  return node_or_markup
548
539
  end
@@ -562,17 +553,15 @@ class Leptris::XML::Element < Leptris::XML::Node
562
553
  if native_fast_children?
563
554
  r = Leptris::XML::Native.append_markup(
564
555
  @document, @c_address, node_or_markup)
565
- if r.is_a?(Integer) && r >= 0
556
+ if r != -1
557
+ # Appends raise in C on status failures (TODO.perf/36);
558
+ # -1 alone means the parse failed.
566
559
  return Leptris::XML::NodeSet.new(@document, []) if r.zero?
567
560
  return Leptris::XML::NodeSet.new(@document, last_added(r))
568
561
  end
569
- if r == -1
570
- # parse failure: reproduce the exact error via the
571
- # legacy path (it re-fails fast)
572
- Leptris::XML::DocumentFragment.parse(node_or_markup, @document)
573
- else
574
- Leptris::XML::FFI.check_status(-r - 1000)
575
- end
562
+ # parse failure: reproduce the exact error via the legacy
563
+ # path (it re-fails fast)
564
+ Leptris::XML::DocumentFragment.parse(node_or_markup, @document)
576
565
  end
577
566
  frag = Leptris::XML::DocumentFragment.parse(node_or_markup, @document)
578
567
  added = []
@@ -486,8 +486,7 @@ class Leptris::XML::Node
486
486
 
487
487
  def unlink
488
488
  if @native_fast
489
- Leptris::XML::FFI.check_status(
490
- Leptris::XML::Native.unlink_binding_node(@document, @c_address))
489
+ Leptris::XML::Native.unlink_binding_node(@document, @c_address)
491
490
  @parent = nil
492
491
  return self
493
492
  end
@@ -55,6 +55,10 @@ module Leptris::XML::Searchable
55
55
  expr = paths.one? && paths.first.is_a?(String) ? paths.first
56
56
  : paths.join(" | ")
57
57
 
58
+ # TODO.perf/37: the document's Pointer is lazy (materializes
59
+ # once per document lifetime, not per parse); the ffi gem
60
+ # does NOT convert Integers for typedef'd pointer params, so
61
+ # the Pointer object is still what crosses here.
58
62
  doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
59
63
  context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
60
64
 
@@ -100,6 +104,10 @@ module Leptris::XML::Searchable
100
104
  expr = paths.one? && paths.first.is_a?(String) ? paths.first
101
105
  : paths.join(" | ")
102
106
 
107
+ # TODO.perf/37: the document's Pointer is lazy (materializes
108
+ # once per document lifetime, not per parse); the ffi gem
109
+ # does NOT convert Integers for typedef'd pointer params, so
110
+ # the Pointer object is still what crosses here.
103
111
  doc_ptr = is_a?(Leptris::XML::Document) ? c_ptr : document.c_ptr
104
112
  context_ptr = is_a?(Leptris::XML::Document) ? nil : c_ptr
105
113
 
@@ -32,16 +32,16 @@ module Leptris::XML::Serialization
32
32
  # cycle lives at the FFI seam (FFI.serialize_into_string); this
33
33
  # module owns only options selection and construction.
34
34
  def self.to_xml(ffi_function, c_ptr, indent: 0, no_decl: false, encoding: nil)
35
- # TODO.perf/04: the common shape (no encoding) runs the whole
36
- # sized-buffer cycle in the ext — one C dispatch, no options
37
- # marshaling, no Ruby buffer management.
38
- if defined?(Leptris::XML::NATIVE_FAST) && encoding.nil?
35
+ # TODO.perf/04+36: the whole sized-buffer cycle in the ext —
36
+ # one C dispatch, no options marshaling, no Ruby buffer
37
+ # management; encoding rides the face (nil -> no override).
38
+ if defined?(Leptris::XML::NATIVE_FAST)
39
39
  decl = !no_decl
40
40
  return Leptris::XML::Native.fast_element_xml(
41
- c_ptr.address, indent.to_i, decl) if
41
+ c_ptr.address, indent.to_i, decl, encoding) if
42
42
  ffi_function == ELEMENT_SERIALIZE_INTO
43
43
  return Leptris::XML::Native.fast_document_xml(
44
- c_ptr.address, indent.to_i, decl) if
44
+ c_ptr.address, indent.to_i, decl, encoding) if
45
45
  ffi_function == DOCUMENT_SERIALIZE_INTO
46
46
  end
47
47
  opts =
@@ -59,7 +59,8 @@ module Leptris::XML::Serialization
59
59
  # loop: no kwargs, no options rebuild, no method allocation —
60
60
  # one FFI fast-path dispatch per child.
61
61
  def self.element_xml_default(c_ptr)
62
- return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true) if
62
+ return Leptris::XML::Native.fast_element_xml(c_ptr.address, 0, true,
63
+ nil) if
63
64
  defined?(Leptris::XML::NATIVE_FAST)
64
65
  Leptris::XML::FFI.serialize_into_string(
65
66
  ELEMENT_SERIALIZE_INTO, c_ptr, DEFAULT_OPTIONS.pointer)
@@ -20,9 +20,8 @@ class Leptris::XML::Text < Leptris::XML::Node
20
20
 
21
21
  def content=(new_content)
22
22
  if @native_fast
23
- Leptris::XML::FFI.check_status(
24
- Leptris::XML::Native.set_binding_text(
25
- @document, @c_address, new_content.to_s))
23
+ Leptris::XML::Native.set_binding_text(
24
+ @document, @c_address, new_content.to_s)
26
25
  return new_content
27
26
  end
28
27
  ensure_writable!
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.9
4
+ version: 1.9.174.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -104,6 +104,8 @@ files:
104
104
  - TODO.perf/33-cbound-root-set.md
105
105
  - TODO.perf/34-fragment-fast-lane.md
106
106
  - TODO.perf/35-parse-default-c-face.md
107
+ - TODO.perf/36-raise-in-c-serializer-encoding.md
108
+ - TODO.perf/37-document-lazy-pointer.md
107
109
  - TODO.restructure/01-constraint-compliance-audit.md
108
110
  - TODO.restructure/02-deep-copy-seam.md
109
111
  - TODO.restructure/03-evaluation-context-seam.md