leptris 1.9.255.1 → 1.9.260.0
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 +17 -0
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +146 -20
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/native_layer.rb +12 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +44 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/dom/root_doc_map.c +8 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +2 -1
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_internal.h +5 -0
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_path.c +31 -0
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +42 -11
- data/vendor-src/libleptris/test/dom/test_dom.cpp +7 -1
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +12 -5
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +67 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 719ba38dafe5b398d166a1f53a629fca56336f077756d23e64bfb287387547ba
|
|
4
|
+
data.tar.gz: d228ec1939792d3e06349b1349917735dc65aa2f34e7dc70b186140fc79ec46e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41ab471e5e9b3cf63ea73db78c096514f059949f2bbcf97bc64ee5fe1c9f6523131b875c1397f18ab0d1157e5ebeeeb36fb892da549d80b2c2114f365996e4b3
|
|
7
|
+
data.tar.gz: ea4e2d9a746e7be541d10abe280e77ed96ce5891f4ec66e2b9c7fda0132f024774a8d3c11c0c818b0b9e2b87013e0a5bb6f57f27813b54ff8ff03b419aa9e293
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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.260.0] - 2026-09-27
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.260 (riding 1.9.256-1.9.260):
|
|
13
|
+
- xquery: numeric variable predicates are positional, not EBV
|
|
14
|
+
(v1.9.256); typed-scalar carriers convert in the single-item
|
|
15
|
+
predicate path (v1.9.259); the bare FLWOR initializer scan
|
|
16
|
+
terminates at outer clause words (v1.9.260).
|
|
17
|
+
|
|
18
|
+
### Performance — engine sync
|
|
19
|
+
|
|
20
|
+
- Lane 18 rounds 1-2 (v1.9.257/258): duplicate-attribute scan via
|
|
21
|
+
a per-element FNV-1a hash prefilter (was an O(N^2) memcmp walk)
|
|
22
|
+
and namebp document resolution before the locked root-map
|
|
23
|
+
lookup — builder append 39.7 -> ~30 ns/child.
|
|
24
|
+
|
|
8
25
|
## [1.9.255.0] - 2026-09-26
|
|
9
26
|
|
|
10
27
|
### Fixed
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.260"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -627,6 +627,60 @@ static void *doc_ptr_of(VALUE document)
|
|
|
627
627
|
/* Per-document identity caches. Both are lazily allocated on the
|
|
628
628
|
* Ruby side (@x ||= {}), so replicate that here: ivar read, create
|
|
629
629
|
* and store when unset. */
|
|
630
|
+
/* #340: per-document wrapper-klass registration. The ivar holds an
|
|
631
|
+
* Array of classes/nil indexed by node kind (the same shape
|
|
632
|
+
* install_child_klasses uses at the klass level). When an entry is
|
|
633
|
+
* set, the walk cache AND the create faces mint that klass
|
|
634
|
+
* (NativeNode subclass, TypedData path) instead of the binding
|
|
635
|
+
* wrapper classes — consumers receive their own contract-carrying
|
|
636
|
+
* nodes straight from C, and their re-wrap drops out. */
|
|
637
|
+
static ID id_iv_doc_klass_map;
|
|
638
|
+
|
|
639
|
+
static VALUE doc_klass_for_kind(VALUE document, int kind)
|
|
640
|
+
{
|
|
641
|
+
VALUE map = rb_ivar_get(document, id_iv_doc_klass_map);
|
|
642
|
+
VALUE k;
|
|
643
|
+
if (NIL_P(map)) return Qnil;
|
|
644
|
+
k = rb_ary_entry(map, (long)kind);
|
|
645
|
+
return RB_TYPE_P(k, T_CLASS) ? k : Qnil;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
static VALUE mint_consumer_node(VALUE klass, VALUE document, void *ptr)
|
|
649
|
+
{
|
|
650
|
+
struct native_node *n;
|
|
651
|
+
VALUE node =
|
|
652
|
+
TypedData_Make_Struct(klass, struct native_node, &nn_type, n);
|
|
653
|
+
n->ptr = ptr;
|
|
654
|
+
n->document = document;
|
|
655
|
+
rb_hash_aset(native_cache_of(document),
|
|
656
|
+
ULL2NUM((uint64_t)(uintptr_t)ptr), node);
|
|
657
|
+
return node;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
static VALUE nf_register_doc_klasses(VALUE self, VALUE document, VALUE map)
|
|
661
|
+
{
|
|
662
|
+
long i, len;
|
|
663
|
+
(void)self;
|
|
664
|
+
resolve_binding_classes();
|
|
665
|
+
Check_Type(map, T_ARRAY);
|
|
666
|
+
len = RARRAY_LEN(map);
|
|
667
|
+
if (len > 5)
|
|
668
|
+
rb_raise(rb_eArgError,
|
|
669
|
+
"wrapper klasses: Array of classes/nil indexed by "
|
|
670
|
+
"node kind (element, text, comment, cdata, pi)");
|
|
671
|
+
for (i = 0; i < len; i++) {
|
|
672
|
+
VALUE k = rb_ary_entry(map, i);
|
|
673
|
+
if (!NIL_P(k) &&
|
|
674
|
+
!(RB_TYPE_P(k, T_CLASS) &&
|
|
675
|
+
RTEST(rb_class_inherited_p(k, c_native_node))))
|
|
676
|
+
rb_raise(rb_eTypeError,
|
|
677
|
+
"wrapper klasses entries must be nil or a "
|
|
678
|
+
"Leptris::XML::NativeNode subclass");
|
|
679
|
+
}
|
|
680
|
+
rb_ivar_set(document, id_iv_doc_klass_map, map);
|
|
681
|
+
return document;
|
|
682
|
+
}
|
|
683
|
+
|
|
630
684
|
static VALUE native_cache_of(VALUE document)
|
|
631
685
|
{
|
|
632
686
|
VALUE cache = rb_ivar_get(document, id_iv_native_cache);
|
|
@@ -877,7 +931,20 @@ static VALUE trav_wrapper_for(struct trav_state *st, void *node_ptr,
|
|
|
877
931
|
int kind)
|
|
878
932
|
{
|
|
879
933
|
VALUE key = ULL2NUM((uint64_t)(uintptr_t)node_ptr);
|
|
880
|
-
VALUE node
|
|
934
|
+
VALUE node;
|
|
935
|
+
VALUE ck = doc_klass_for_kind(st->document, kind);
|
|
936
|
+
|
|
937
|
+
/* #340 klass-parameterized walk cache: registered consumer
|
|
938
|
+
* klasses mint (and identity-cache) through the TypedData
|
|
939
|
+
* path — nodes arrive already being the consumer's wrapper. */
|
|
940
|
+
if (!NIL_P(ck)) {
|
|
941
|
+
VALUE ncache = native_cache_of(st->document);
|
|
942
|
+
node = rb_hash_aref(ncache, key);
|
|
943
|
+
if (!NIL_P(node)) return node;
|
|
944
|
+
return mint_consumer_node(ck, st->document, node_ptr);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
node = rb_hash_aref(st->cache, key);
|
|
881
948
|
if (!NIL_P(node)) return node;
|
|
882
949
|
|
|
883
950
|
node = rb_obj_alloc(binding_klass_for(kind));
|
|
@@ -2290,20 +2357,15 @@ static VALUE nf_plan_structs_typed_p(VALUE self)
|
|
|
2290
2357
|
* C->C dlsym dispatches (create_child = create+attach in one
|
|
2291
2358
|
* engine call; set_attr per attribute). Returns the new node's
|
|
2292
2359
|
* address for wrapper minting. */
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
2303
|
-
const char *cname = StringValueCStr(name);
|
|
2304
|
-
|
|
2305
|
-
if (f_elem_new_with_attrs && doc_ptr && parent && RARRAY_LEN(attrs) > 0 &&
|
|
2306
|
-
RARRAY_LEN(attrs) % 2 == 0) {
|
|
2360
|
+
/* Creation core shared by the raw-address face and the #340
|
|
2361
|
+
* wrapper-returning variant: single-crossing arena create +
|
|
2362
|
+
* attributes when the engine has #1344, else create + per-pair
|
|
2363
|
+
* sets. Returns the raw element pointer (NULL on failure). */
|
|
2364
|
+
static void *create_elem_core(void *doc_ptr, void *parent,
|
|
2365
|
+
const char *cname, VALUE attrs)
|
|
2366
|
+
{
|
|
2367
|
+
if (f_elem_new_with_attrs && doc_ptr && parent &&
|
|
2368
|
+
RARRAY_LEN(attrs) > 0 && RARRAY_LEN(attrs) % 2 == 0) {
|
|
2307
2369
|
/* Engine single-crossing construction (1.9.236+): arena
|
|
2308
2370
|
* create + all attributes with no per-attr calls at all. */
|
|
2309
2371
|
long n = RARRAY_LEN(attrs) / 2;
|
|
@@ -2319,15 +2381,15 @@ static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
|
|
|
2319
2381
|
}
|
|
2320
2382
|
void *elem = f_elem_new_with_attrs(doc_ptr, cname, names,
|
|
2321
2383
|
values, n);
|
|
2322
|
-
if (!elem) return
|
|
2384
|
+
if (!elem) return NULL;
|
|
2323
2385
|
/* The face creates a floating element owned by the
|
|
2324
2386
|
* document — append under the parent afterwards. */
|
|
2325
|
-
|
|
2326
|
-
return
|
|
2387
|
+
f_append_child(parent, elem);
|
|
2388
|
+
return elem;
|
|
2327
2389
|
}
|
|
2328
2390
|
|
|
2329
2391
|
void *elem = parent ? f_create_child(parent, cname) : NULL;
|
|
2330
|
-
if (!elem) return
|
|
2392
|
+
if (!elem) return NULL;
|
|
2331
2393
|
|
|
2332
2394
|
long i, n = RARRAY_LEN(attrs);
|
|
2333
2395
|
for (i = 0; i + 1 < n; i += 2) {
|
|
@@ -2335,7 +2397,65 @@ static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
|
|
|
2335
2397
|
VALUE av = RARRAY_AREF(attrs, i + 1);
|
|
2336
2398
|
f_set_attr(elem, StringValueCStr(an), StringValueCStr(av));
|
|
2337
2399
|
}
|
|
2338
|
-
return
|
|
2400
|
+
return elem;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
|
|
2404
|
+
VALUE parent_addr, VALUE name,
|
|
2405
|
+
VALUE attrs)
|
|
2406
|
+
{
|
|
2407
|
+
void *doc_ptr = NIL_P(document)
|
|
2408
|
+
? NULL
|
|
2409
|
+
: (void *)(uintptr_t)NUM2ULL(document);
|
|
2410
|
+
void *parent = NIL_P(parent_addr)
|
|
2411
|
+
? NULL
|
|
2412
|
+
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
2413
|
+
void *elem = create_elem_core(doc_ptr, parent,
|
|
2414
|
+
StringValueCStr(name), attrs);
|
|
2415
|
+
return elem ? ULL2NUM((uintptr_t)elem) : Qnil;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
/* #340 construction registration: create + attach + attributes in
|
|
2419
|
+
* one crossing AND mint the wrapper C-side — the registered
|
|
2420
|
+
* consumer klass when one is installed for elements (TypedData
|
|
2421
|
+
* path + native-cache identity), else the binding Element minted
|
|
2422
|
+
* exactly like nf_create_binding_element. The caller receives a
|
|
2423
|
+
* contract-carrying node straight from the crossing; no separate
|
|
2424
|
+
* Node.wrap, no consumer re-wrap. */
|
|
2425
|
+
static VALUE nf_create_element_with_attrs_wrapped(
|
|
2426
|
+
VALUE self, VALUE document, VALUE parent_addr, VALUE name,
|
|
2427
|
+
VALUE attrs)
|
|
2428
|
+
{
|
|
2429
|
+
void *doc_ptr = doc_ptr_of(document);
|
|
2430
|
+
void *parent = NIL_P(parent_addr)
|
|
2431
|
+
? NULL
|
|
2432
|
+
: (void *)(uintptr_t)NUM2ULL(parent_addr);
|
|
2433
|
+
void *elem = create_elem_core(doc_ptr, parent,
|
|
2434
|
+
StringValueCStr(name), attrs);
|
|
2435
|
+
VALUE ck, key, node, cache;
|
|
2436
|
+
|
|
2437
|
+
(void)self;
|
|
2438
|
+
if (!elem) return Qnil;
|
|
2439
|
+
resolve_binding_classes();
|
|
2440
|
+
ck = doc_klass_for_kind(document, 0 /* element */);
|
|
2441
|
+
if (!NIL_P(ck))
|
|
2442
|
+
return mint_consumer_node(ck, document, elem);
|
|
2443
|
+
|
|
2444
|
+
key = ULL2NUM((uint64_t)(uintptr_t)elem);
|
|
2445
|
+
cache = binding_cache_of(document);
|
|
2446
|
+
node = rb_hash_aref(cache, key);
|
|
2447
|
+
if (!NIL_P(node)) return node;
|
|
2448
|
+
node = rb_obj_alloc(c_b_element);
|
|
2449
|
+
rb_iv_set(node, "@c_address", key);
|
|
2450
|
+
rb_iv_set(node, "@document", document);
|
|
2451
|
+
rb_iv_set(node, "@parent", Qnil);
|
|
2452
|
+
rb_iv_set(node, "@structure_memoizable", Qtrue);
|
|
2453
|
+
rb_iv_set(node, "@native_fast", Qtrue);
|
|
2454
|
+
rb_iv_set(node, "@pub_document", document);
|
|
2455
|
+
rb_iv_set(node, "@addr_reads_fast", Qtrue);
|
|
2456
|
+
rb_iv_set(node, "@node_type", INT2FIX(0));
|
|
2457
|
+
rb_hash_aset(cache, key, node);
|
|
2458
|
+
return node;
|
|
2339
2459
|
}
|
|
2340
2460
|
|
|
2341
2461
|
static VALUE nf_attribute_rows(VALUE self, VALUE document, VALUE addr)
|
|
@@ -2844,6 +2964,7 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
2844
2964
|
id_iv_nn_attrs = rb_intern("@nn_attrs");
|
|
2845
2965
|
id_iv_nn_attrs_ver = rb_intern("@nn_attrs_ver");
|
|
2846
2966
|
id_child_klasses = rb_intern("@nn_child_klasses");
|
|
2967
|
+
id_iv_doc_klass_map = rb_intern("@doc_klass_map");
|
|
2847
2968
|
id_iv_doc_handle = rb_intern("@doc_handle");
|
|
2848
2969
|
id_freed_new = rb_intern("new");
|
|
2849
2970
|
id_alive = rb_intern("alive");
|
|
@@ -2954,6 +3075,11 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
2954
3075
|
nf_attribute_rows, 2);
|
|
2955
3076
|
rb_define_module_function(m_native, "create_element_with_attrs",
|
|
2956
3077
|
nf_create_element_with_attrs, 4);
|
|
3078
|
+
rb_define_module_function(m_native, "register_doc_klasses",
|
|
3079
|
+
nf_register_doc_klasses, 2);
|
|
3080
|
+
rb_define_module_function(m_native,
|
|
3081
|
+
"create_element_with_attrs_wrapped",
|
|
3082
|
+
nf_create_element_with_attrs_wrapped, 4);
|
|
2957
3083
|
rb_define_module_function(m_native, "plan_structs_typed?",
|
|
2958
3084
|
nf_plan_structs_typed_p, 0);
|
|
2959
3085
|
rb_define_module_function(m_native, "doc_handle_attach",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
|
@@ -75,6 +75,18 @@ class Leptris::XML::Document
|
|
|
75
75
|
Leptris::XML::NativeNode.from(self, r.c_ptr)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# #340: per-kind wrapper-klass registration for THIS document.
|
|
79
|
+
# map is an Array of classes/nil indexed by node kind (element,
|
|
80
|
+
# text, comment, cdata, pi) — the same shape
|
|
81
|
+
# NativeNode.install_child_klasses uses. Once registered, the C
|
|
82
|
+
# walk cache (trav/visit) and the create faces mint those
|
|
83
|
+
# klasses directly (TypedData path, native-cache identity), so
|
|
84
|
+
# consumers receive their own contract-carrying nodes straight
|
|
85
|
+
# from the crossing with no re-wrap.
|
|
86
|
+
def register_wrapper_klasses(map)
|
|
87
|
+
Leptris::XML::Native.register_doc_klasses(self, map)
|
|
88
|
+
end
|
|
89
|
+
|
|
78
90
|
# Builder factories that return NativeNodes (#149): one C call
|
|
79
91
|
# and a TypedData wrap — no FFI::Pointer, no wrap_fresh path.
|
|
80
92
|
def native_create_element(name)
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.260] - 2026-09-27
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- bare FLWOR initializer scan terminates at outer clause words (xquery)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [1.9.259] - 2026-09-26
|
|
12
|
+
|
|
13
|
+
<!-- Edit this section with the actual release notes. -->
|
|
14
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- (describe changes here)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## [1.9.258] - 2026-09-26
|
|
22
|
+
|
|
23
|
+
<!-- Edit this section with the actual release notes. -->
|
|
24
|
+
<!-- See https://keepachangelog.com for format guidance. -->
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- (describe changes here)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## [1.9.257] - 2026-09-26
|
|
32
|
+
|
|
33
|
+
### Performance
|
|
34
|
+
|
|
35
|
+
- resolve namebp documents before the locked root-map lookup (dom)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## [1.9.256] - 2026-09-26
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- numeric variable predicates are positional, not EBV (xquery)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
3
47
|
## [1.9.255] - 2026-09-26
|
|
4
48
|
|
|
5
49
|
<!-- Edit this section with the actual release notes. -->
|
|
@@ -414,6 +414,14 @@ struct leptris_document* leptris_element_get_document(LeptrisElement elem) {
|
|
|
414
414
|
if (!parent) break;
|
|
415
415
|
cur = parent;
|
|
416
416
|
}
|
|
417
|
+
/* Lane 18 P2: resolve the namebp backpointer BEFORE the locked
|
|
418
|
+
* map. Fast-path-created roots (mut_elem_carve) carry the doc
|
|
419
|
+
* statelessly in the name slot and never register, so the map
|
|
420
|
+
* below always misses for them — the append/builder loop paid
|
|
421
|
+
* the mutex on every call to reach the same answer. The namebp
|
|
422
|
+
* value is stamped at create and cannot go stale. */
|
|
423
|
+
if (cur->name && leptris_elem_has_namebp(cur))
|
|
424
|
+
return leptris_elem_namebp_doc(cur);
|
|
417
425
|
/* Locked bucket walk with an UNDER-THE-LOCK generation
|
|
418
426
|
* snapshot: a death that acquires the lock after we release
|
|
419
427
|
* bumps the global generation past our snapshot, so the primed
|
|
@@ -1004,7 +1004,8 @@ struct leptris_xpath_result* evaluate_expr(XPathContext* ctx, XPathASTNode* ast)
|
|
|
1004
1004
|
ctx->context_position = 1;
|
|
1005
1005
|
ctx->context_size = 1;
|
|
1006
1006
|
struct leptris_xpath_result* pr =
|
|
1007
|
-
|
|
1007
|
+
leptris_pred_scalar_convert(
|
|
1008
|
+
evaluate_expr(ctx, pred));
|
|
1008
1009
|
ctx->context_node = old_node;
|
|
1009
1010
|
ctx->context_position = old_pos;
|
|
1010
1011
|
ctx->context_size = old_size;
|
|
@@ -76,6 +76,11 @@ struct leptris_xpath_result* evaluate_expr(XPathContext* ctx, XPathASTNode* ast)
|
|
|
76
76
|
/* Marker-aware atomic eq over raw item strings (evaluator_operators.c). */
|
|
77
77
|
int leptris_atom_seq_eq_n(const char* a, const char* b, int nan_equal);
|
|
78
78
|
|
|
79
|
+
/* Typed-scalar carrier conversion for predicates (evaluator_path.c):
|
|
80
|
+
* takes ownership; single numeric-marker carriers become NUMBER. */
|
|
81
|
+
struct leptris_xpath_result* leptris_pred_scalar_convert(
|
|
82
|
+
struct leptris_xpath_result* r);
|
|
83
|
+
|
|
79
84
|
/* xs:time lexical -> timezone-normalized seconds (cycle = mod 86400
|
|
80
85
|
* for equality; linear = unbounded for ordering); either out may be
|
|
81
86
|
* NULL. Returns 0 when s is not a time lexical. */
|
|
@@ -281,6 +281,34 @@ int matches_node_test(XPathContext* ctx, LeptrisNode* node, XPathASTNode* test)
|
|
|
281
281
|
/* Helper: Evaluate a single predicate for a node in-place
|
|
282
282
|
* Returns 1 if node matches predicate, 0 otherwise
|
|
283
283
|
*/
|
|
284
|
+
/* Typed-scalar carrier conversion for predicates: the xq driver
|
|
285
|
+
* binds every variable as a sequence carrier, so a numeric atomic
|
|
286
|
+
* rides one marked text node. A bare variable predicate must then be
|
|
287
|
+
* POSITIONAL (number -> position compare), not EBV-of-a-one-item
|
|
288
|
+
* sequence (cbcl-distinct-values-007). Takes ownership of r; when
|
|
289
|
+
* converted, the carrier copy is freed through the result and a
|
|
290
|
+
* fresh NUMBER result returns. NULL propagates. */
|
|
291
|
+
struct leptris_xpath_result* leptris_pred_scalar_convert(
|
|
292
|
+
struct leptris_xpath_result* r) {
|
|
293
|
+
if (!r || r->type != XPATH_RESULT_NODESET || !r->value.nodeset_value ||
|
|
294
|
+
r->value.nodeset_value->count != 1 ||
|
|
295
|
+
!r->value.nodeset_value->nodes[0] ||
|
|
296
|
+
XPATH_NODE_TYPE(r->value.nodeset_value->nodes[0]) !=
|
|
297
|
+
LEPTRIS_NODE_TEXT)
|
|
298
|
+
return r;
|
|
299
|
+
const char* c =
|
|
300
|
+
((XPathTextNode*)r->value.nodeset_value->nodes[0])->content;
|
|
301
|
+
if (!c || c[0] != '\x03' ||
|
|
302
|
+
(c[1] != 'N' && c[1] != 'F' && c[1] != 'D'))
|
|
303
|
+
return r;
|
|
304
|
+
double v = strtod(c + 2, NULL);
|
|
305
|
+
xpath_result_free(r);
|
|
306
|
+
struct leptris_xpath_result* out =
|
|
307
|
+
xpath_result_new(XPATH_RESULT_NUMBER);
|
|
308
|
+
if (out) out->value.number_value = v;
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
284
312
|
static int evaluate_predicate_for_node(XPathContext* ctx,
|
|
285
313
|
void* node,
|
|
286
314
|
XPathASTNode* predicate,
|
|
@@ -328,6 +356,9 @@ static int evaluate_predicate_for_node(XPathContext* ctx,
|
|
|
328
356
|
struct leptris_xpath_result* pred_result = evaluate_expr(ctx, predicate);
|
|
329
357
|
int matches = 0;
|
|
330
358
|
|
|
359
|
+
pred_result = leptris_pred_scalar_convert(pred_result);
|
|
360
|
+
if (!pred_result) return 0;
|
|
361
|
+
|
|
331
362
|
if (pred_result) {
|
|
332
363
|
/* Numeric predicate: matches position */
|
|
333
364
|
if (pred_result->type == XPATH_RESULT_NUMBER) {
|
|
@@ -173,24 +173,39 @@ static int is_clause_word(const char* w, size_t len) {
|
|
|
173
173
|
/* Advance over one FLWOR expression segment: stops before a clause
|
|
174
174
|
* keyword at nesting depth 0 (a bare word delimited by whitespace
|
|
175
175
|
* or segment end). */
|
|
176
|
-
static void scan_expr_segment(Scan* s, int stop_at_comma
|
|
176
|
+
static void scan_expr_segment(Scan* s, int stop_at_comma,
|
|
177
|
+
int allow_bare_flwor) {
|
|
177
178
|
int depth = 0;
|
|
179
|
+
/* A binding expression that STARTS with for/let is a bare
|
|
180
|
+
* nested FLWOR (let $x := for ... return ...): its clause
|
|
181
|
+
* keywords are inert and `return` closes the nesting. Segments
|
|
182
|
+
* that merely CONTAIN a depth-0 for/let after a complete
|
|
183
|
+
* expression (for $i in (1,2,3) let ...) keep the old boundary.
|
|
184
|
+
* allow_bare_flwor arms the check for the first word only. */
|
|
185
|
+
int flwor_nest = 0;
|
|
186
|
+
int seen_token = 0; /* any non-space token consumed yet? */
|
|
178
187
|
while (s->p < s->end) {
|
|
179
188
|
char c = *s->p;
|
|
180
189
|
if (c == '\'' || c == '"') {
|
|
181
190
|
scan_string(s);
|
|
191
|
+
seen_token = 1;
|
|
182
192
|
continue;
|
|
183
193
|
}
|
|
184
194
|
if (c == '`') {
|
|
185
195
|
s->p++;
|
|
186
196
|
while (s->p < s->end && *s->p != '`') s->p++;
|
|
187
197
|
if (s->p < s->end) s->p++;
|
|
198
|
+
seen_token = 1;
|
|
188
199
|
continue;
|
|
189
200
|
}
|
|
190
|
-
if (c == '(' || c == '[' || c == '{')
|
|
201
|
+
if (c == '(' || c == '[' || c == '{') {
|
|
202
|
+
depth++;
|
|
203
|
+
seen_token = 1;
|
|
204
|
+
}
|
|
191
205
|
else if (c == ')' || c == ']' || c == '}') {
|
|
192
206
|
if (depth == 0) return; /* segment boundary */
|
|
193
207
|
depth--;
|
|
208
|
+
seen_token = 1;
|
|
194
209
|
} else if (c == ',' && depth == 0 && stop_at_comma) {
|
|
195
210
|
return;
|
|
196
211
|
} else if (depth == 0 &&
|
|
@@ -202,9 +217,25 @@ static void scan_expr_segment(Scan* s, int stop_at_comma) {
|
|
|
202
217
|
* by whitespace/segment end. scan_word does not
|
|
203
218
|
* advance the cursor — look past the word. */
|
|
204
219
|
const char* after = w + wl;
|
|
205
|
-
|
|
206
|
-
|
|
220
|
+
int at_boundary =
|
|
221
|
+
after >= s->end || isspace((unsigned char)*after);
|
|
222
|
+
if (at_boundary && !seen_token && allow_bare_flwor &&
|
|
223
|
+
(word_is(w, wl, "for") || word_is(w, wl, "let"))) {
|
|
224
|
+
flwor_nest = 1;
|
|
225
|
+
seen_token = 1;
|
|
226
|
+
s->p += wl;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (at_boundary && flwor_nest > 0) {
|
|
230
|
+
if (word_is(w, wl, "return")) flwor_nest--;
|
|
231
|
+
seen_token = 1;
|
|
232
|
+
s->p += wl;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
seen_token = 1;
|
|
236
|
+
if (at_boundary) return;
|
|
207
237
|
}
|
|
238
|
+
seen_token = 1;
|
|
208
239
|
s->p += wl ? wl : 1;
|
|
209
240
|
continue;
|
|
210
241
|
}
|
|
@@ -1424,7 +1455,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1424
1455
|
}
|
|
1425
1456
|
s.p = iw + iwl;
|
|
1426
1457
|
Scan e = s;
|
|
1427
|
-
scan_expr_segment(&e, 0);
|
|
1458
|
+
scan_expr_segment(&e, 0, 0);
|
|
1428
1459
|
wc.win.domain = parse_expr_span(s.p, e.p);
|
|
1429
1460
|
if (!wc.win.domain) {
|
|
1430
1461
|
xq_clause_partial_free(&wc);
|
|
@@ -1471,7 +1502,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1471
1502
|
if (wwl2 && word_is(ww, wwl2, "when")) {
|
|
1472
1503
|
s.p = ww + wwl2;
|
|
1473
1504
|
Scan se = s;
|
|
1474
|
-
scan_expr_segment(&se, 0);
|
|
1505
|
+
scan_expr_segment(&se, 0, 0);
|
|
1475
1506
|
wc.win.s_when =
|
|
1476
1507
|
parse_expr_span(s.p, se.p);
|
|
1477
1508
|
if (!wc.win.s_when) {
|
|
@@ -1520,7 +1551,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1520
1551
|
if (wwl2 && word_is(ww, wwl2, "when")) {
|
|
1521
1552
|
s.p = ww + wwl2;
|
|
1522
1553
|
Scan ee = s;
|
|
1523
|
-
scan_expr_segment(&ee, 0);
|
|
1554
|
+
scan_expr_segment(&ee, 0, 0);
|
|
1524
1555
|
wc.win.e_when =
|
|
1525
1556
|
parse_expr_span(s.p, ee.p);
|
|
1526
1557
|
if (!wc.win.e_when) {
|
|
@@ -1616,7 +1647,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1616
1647
|
s.p += 2;
|
|
1617
1648
|
}
|
|
1618
1649
|
Scan e = s;
|
|
1619
|
-
scan_expr_segment(&e, 1);
|
|
1650
|
+
scan_expr_segment(&e, 1, 1);
|
|
1620
1651
|
XPathASTNode* expr = parse_expr_span(s.p, e.p);
|
|
1621
1652
|
if (!expr) {
|
|
1622
1653
|
free(var);
|
|
@@ -1649,7 +1680,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1649
1680
|
}
|
|
1650
1681
|
} else if (word_is(kw, kwl, "where")) {
|
|
1651
1682
|
Scan e = s;
|
|
1652
|
-
scan_expr_segment(&e, 0);
|
|
1683
|
+
scan_expr_segment(&e, 0, 0);
|
|
1653
1684
|
/* WHERE after GROUP BY filters the GROUPED
|
|
1654
1685
|
* tuples (aggregates see the whole group) */
|
|
1655
1686
|
XPathASTNode* w_ast = parse_expr_span(s.p, e.p);
|
|
@@ -1722,7 +1753,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1722
1753
|
s.p[1] == '=') {
|
|
1723
1754
|
s.p += 2;
|
|
1724
1755
|
Scan e = s;
|
|
1725
|
-
scan_expr_segment(&e, 0);
|
|
1756
|
+
scan_expr_segment(&e, 0, 0);
|
|
1726
1757
|
gk = parse_expr_span(s.p, e.p);
|
|
1727
1758
|
if (!gk) {
|
|
1728
1759
|
free(gvar);
|
|
@@ -1800,7 +1831,7 @@ LEPTRIS_API LeptrisXQuery leptris_xquery_parse(const char* query,
|
|
|
1800
1831
|
for (;;) {
|
|
1801
1832
|
scan_ws(&s);
|
|
1802
1833
|
Scan e = s;
|
|
1803
|
-
scan_expr_segment(&e, 1); /* stop at ',' */
|
|
1834
|
+
scan_expr_segment(&e, 1, 1); /* stop at ',' */
|
|
1804
1835
|
XPathASTNode* key = parse_expr_span(s.p, e.p);
|
|
1805
1836
|
if (!key) {
|
|
1806
1837
|
xq_free(q);
|
|
@@ -160,7 +160,13 @@ TEST(DomBasics, DocumentSetRootRejectsParentedElement) {
|
|
|
160
160
|
TEST(DomBasics, RootDocMemoClearedWhenRootUnregisters) {
|
|
161
161
|
LeptrisDocument doc = leptris_document_create();
|
|
162
162
|
ASSERT_NE(doc, nullptr);
|
|
163
|
-
|
|
163
|
+
/* A >254-char name forces the pool-fallback create: no namebp
|
|
164
|
+
* backpointer, so the element is MAP-resolved. Lane 18 P2 made
|
|
165
|
+
* namebp-carrying elements resolve statelessly before the map
|
|
166
|
+
* (no memo to prime, nothing to invalidate); the unregistration
|
|
167
|
+
* contract below is about the map-primed memo. */
|
|
168
|
+
std::string long_name(300, 'x');
|
|
169
|
+
LeptrisElement e = leptris_element_create(doc, long_name.c_str());
|
|
164
170
|
ASSERT_NE(e, nullptr);
|
|
165
171
|
leptris_root_doc_register(e, doc);
|
|
166
172
|
/* Prime the memo through a resolution. */
|
|
@@ -983,14 +983,21 @@ TEST(Qt3Subset, FnDistinctValues) {
|
|
|
983
983
|
* fn-distinct-values-1 (Bugzilla 5183: float must promote UP to
|
|
984
984
|
* double, never the reverse) close with F&O promotion in the
|
|
985
985
|
* dedup comparator. */
|
|
986
|
-
/*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
|
|
986
|
+
/* cbcl-distinct-values-007: both halves are in (tz-normalized
|
|
987
|
+
* time equality + typed-scalar predicates) and the query is
|
|
988
|
+
* correct on macOS, but Linux/ASAN still evaluates
|
|
989
|
+
* xs:dayTimeDuration("PT0S")[$p] with the at-bound position as
|
|
990
|
+
* EBV-true (all-Z output) — a narrow platform divergence to
|
|
991
|
+
* chase separately. */
|
|
992
|
+
/* cbcl-distinct-values-007 now passes everywhere: BOTH predicate
|
|
993
|
+
* paths convert typed-scalar carriers (the nodeset-base path via
|
|
994
|
+
* evaluate_predicate_for_node AND the single-item base path in
|
|
995
|
+
* the XPATH_AST_PREDICATE handler — xs:dayTimeDuration("PT0S")[$p]
|
|
996
|
+
* takes the latter). */
|
|
997
|
+
run_test_set("fn/distinct-values.xml", {}, 99, {},
|
|
990
998
|
{
|
|
991
999
|
"cbcl-distinct-values-002",
|
|
992
1000
|
"cbcl-distinct-values-002b",
|
|
993
|
-
"cbcl-distinct-values-007",
|
|
994
1001
|
|
|
995
1002
|
});
|
|
996
1003
|
}
|
|
@@ -66,6 +66,27 @@ TEST(XQueryCore, PrologVariableAndFlwor) {
|
|
|
66
66
|
leptris_document_free(doc);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
TEST(XQueryCore, BareFlworInitializer) {
|
|
70
|
+
/* A binding expression may itself start a FLWOR without
|
|
71
|
+
* parentheses; the OUTER clause words (let/order by/where/
|
|
72
|
+
* return) must still terminate the inner scan. Regression
|
|
73
|
+
* shape from QT3 K2-OrderbyExprWithout. */
|
|
74
|
+
LeptrisDocument doc = leptris_parse_string("<r/>", 4, nullptr);
|
|
75
|
+
ASSERT_NE(doc, nullptr);
|
|
76
|
+
EXPECT_EQ(seq_string(doc,
|
|
77
|
+
"let $x := for $i in (1,2) return $i * 10 return $x"),
|
|
78
|
+
"10 20");
|
|
79
|
+
EXPECT_EQ(seq_string(doc,
|
|
80
|
+
"for $a in (1, 4, 2) let $i := (1, 3, 2) "
|
|
81
|
+
"order by $a return $i"),
|
|
82
|
+
"1 3 2 1 3 2 1 3 2");
|
|
83
|
+
EXPECT_EQ(seq_string(doc,
|
|
84
|
+
"for $i in (1, 2, 3) let $s := $i * $i "
|
|
85
|
+
"where $i > 1 return $s"),
|
|
86
|
+
"4 9");
|
|
87
|
+
leptris_document_free(doc);
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
TEST(XQueryCore, OrderByDescending) {
|
|
70
91
|
LeptrisDocument doc = leptris_parse_string(kBooks, strlen(kBooks),
|
|
71
92
|
nullptr);
|
|
@@ -1215,6 +1236,52 @@ TEST(XQueryCore, MixedNumericCompare) {
|
|
|
1215
1236
|
}
|
|
1216
1237
|
|
|
1217
1238
|
|
|
1239
|
+
TEST(XQueryCore, VarPredicatePositional) {
|
|
1240
|
+
LeptrisDocument doc = leptris_parse_string("<e/>", 4, nullptr);
|
|
1241
|
+
ASSERT_NE(doc, nullptr);
|
|
1242
|
+
struct {
|
|
1243
|
+
const char* q;
|
|
1244
|
+
const char* want;
|
|
1245
|
+
} cases[] = {
|
|
1246
|
+
/* a numeric variable is a POSITIONAL predicate: position()
|
|
1247
|
+
* = $p — not EBV. The xq driver binds vars as sequence
|
|
1248
|
+
* carriers, so the bare-var predicate took the EBV-true
|
|
1249
|
+
* path and kept the whole sequence. */
|
|
1250
|
+
{"let $p := 2 return count((10,20,30)[$p])", "1"},
|
|
1251
|
+
{"let $p := 2 return (10,20,30)[$p]", "20"},
|
|
1252
|
+
{"string-join(for $p in (2,3) return"
|
|
1253
|
+
" string((10,20,30)[$p]), '|')", "20|30"},
|
|
1254
|
+
{"let $p := 2.0 return count((10,20,30)[$p])", "1"},
|
|
1255
|
+
{/* cbcl-distinct-values-007's shape: positional predicate
|
|
1256
|
+
* on a singleton sequence (function-call base) */
|
|
1257
|
+
"let $p := 1 return"
|
|
1258
|
+
" count(xs:dayTimeDuration('PT0S')[$p])", "1"},
|
|
1259
|
+
{/* single-item base path: the XQUERY entry must convert the
|
|
1260
|
+
* carrier in the AST_PREDICATE handler too */
|
|
1261
|
+
"let $p := 2 return"
|
|
1262
|
+
" string(adjust-time-to-timezone(xs:time('12:00:00'),"
|
|
1263
|
+
" (xs:dayTimeDuration('PT0S')[$p])))", "12:00:00"},
|
|
1264
|
+
{"count(distinct-values((xs:time('12:00:00'),"
|
|
1265
|
+
" xs:time('12:00:00'), xs:time('20:00:00'),"
|
|
1266
|
+
" xs:time('01:00:00+12:00'), xs:time('02:00:00+13:00'))))",
|
|
1267
|
+
"3"},
|
|
1268
|
+
};
|
|
1269
|
+
for (auto& c : cases) {
|
|
1270
|
+
LeptrisXQuery xq = leptris_xquery_parse(c.q, strlen(c.q));
|
|
1271
|
+
ASSERT_NE(xq, nullptr) << c.q;
|
|
1272
|
+
LeptrisXPathResult r = leptris_xquery_eval(xq, doc, NULL);
|
|
1273
|
+
ASSERT_NE(r, nullptr) << c.q;
|
|
1274
|
+
char* s = leptris_xpath_result_string(r);
|
|
1275
|
+
ASSERT_NE(s, nullptr) << c.q;
|
|
1276
|
+
EXPECT_STREQ(s, c.want) << c.q;
|
|
1277
|
+
leptris_free_string(s);
|
|
1278
|
+
leptris_xpath_result_free(r);
|
|
1279
|
+
leptris_xquery_free(xq);
|
|
1280
|
+
}
|
|
1281
|
+
leptris_document_free(doc);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
|
|
1218
1285
|
TEST(XQueryCore, TimeTzEq) {
|
|
1219
1286
|
LeptrisDocument doc = leptris_parse_string("<e/>", 4, nullptr);
|
|
1220
1287
|
ASSERT_NE(doc, nullptr);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.260",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|