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 +4 -4
- data/CHANGELOG.md +20 -0
- data/Rakefile +6 -1
- data/TODO.perf/36-raise-in-c-serializer-encoding.md +34 -0
- data/TODO.perf/37-document-lazy-pointer.md +28 -0
- data/ext/leptris/native/native.c +57 -19
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +14 -3
- data/lib/leptris/xml/element.rb +22 -33
- data/lib/leptris/xml/node.rb +1 -2
- data/lib/leptris/xml/searchable.rb +8 -0
- data/lib/leptris/xml/serialization.rb +8 -7
- data/lib/leptris/xml/text.rb +2 -3
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0e347192431e6ed3ff15bed0fca55fb103ba7307a33d889014e72c3ece0dba8
|
|
4
|
+
data.tar.gz: f7edb28afa62063bbc1f0ab2cfcc349619a8944249f69f41b40e054e016b5b45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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.
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
2272
|
+
nf_fast_document_xml, 4);
|
|
2235
2273
|
rb_define_module_function(m_native, "fast_element_xml",
|
|
2236
|
-
nf_fast_element_xml,
|
|
2274
|
+
nf_fast_element_xml, 4);
|
|
2237
2275
|
}
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -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::
|
|
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))
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -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::
|
|
29
|
-
|
|
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::
|
|
58
|
-
|
|
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::
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
unless
|
|
545
|
-
|
|
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
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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 = []
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -486,8 +486,7 @@ class Leptris::XML::Node
|
|
|
486
486
|
|
|
487
487
|
def unlink
|
|
488
488
|
if @native_fast
|
|
489
|
-
Leptris::XML::
|
|
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
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
if defined?(Leptris::XML::NATIVE_FAST)
|
|
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
|
|
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)
|
data/lib/leptris/xml/text.rb
CHANGED
|
@@ -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::
|
|
24
|
-
|
|
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.
|
|
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
|