leptris 1.9.255.1-arm-linux → 1.9.260.0-arm-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: 7b4e0a9650a8476f55d7168393310d84a0f0bc5c4bc68afbcb8da52eb4461b88
4
- data.tar.gz: faeaa46310e9c4034055c61971d3129a0beb92e0aed1dfb3fdb316e40c5a9b83
3
+ metadata.gz: c25ab81a03b81ff63231f86fa857443a3ac86390d638b5b59750e4266bcbf84b
4
+ data.tar.gz: 3fca55520156d69e90faf918c67e3dbbbaa68dd5b7de7f7595cd27fb9e213918
5
5
  SHA512:
6
- metadata.gz: 6b96a662e7a06ecb95d7a5545a7c69d91c071738362837a061017ccccf1fd11e56b05594e2a42b15fb1fe6e1c9e10bc5fc34cc3d668dc35c1d580d2694c0bdca
7
- data.tar.gz: f8ff48b9d740b085b7945cf5a0a9a2a2ccd9582589a8331e86b7f56d9a4226ce014a642bdb4b1141770b51fa00e92036496dd19d19230b7573e84478778265c0
6
+ metadata.gz: 585a0fa7211c194d2d9005ead8a37f2c6bb24328db50f4b02f76f2812c326a38587c0ce4180495b930add301de6efd29ae3e1965feccaab9cb217b50d98b8684
7
+ data.tar.gz: 725de11f34e9565fa08c97f2d75efaded89c96cdd2734baba54d5f39055b0ac71c5918b2127806377726763e4221a1a1b734bb0db73b725f95459f3f860480e8
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.255"
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
@@ -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 = rb_hash_aref(st->cache, key);
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
- static VALUE nf_create_element_with_attrs(VALUE self, VALUE document,
2294
- VALUE parent_addr, VALUE name,
2295
- VALUE attrs)
2296
- {
2297
- void *doc_ptr = NIL_P(document)
2298
- ? NULL
2299
- : (void *)(uintptr_t)NUM2ULL(document);
2300
- void *parent = NIL_P(parent_addr)
2301
- ? NULL
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 Qnil;
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
- if (parent) f_append_child(parent, elem);
2326
- return ULL2NUM((uintptr_t)elem);
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 Qnil;
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 ULL2NUM((uintptr_t)elem);
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",
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.255.1"
4
+ VERSION = "1.9.260.0"
5
5
  end
Binary file
@@ -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/lib/libleptris.so CHANGED
Binary file
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.255
4
+ - libleptris v1.9.260
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -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. -->
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.255
6
+ VERSION 1.9.260
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -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
- evaluate_expr(ctx, pred);
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 == '{') depth++;
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
- if (after >= s->end || isspace((unsigned char)*after))
206
- return;
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
- LeptrisElement e = leptris_element_create(doc, "r");
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
- /* fn-distinct-values-2 adopted: the assert-permutation
987
- * harness expands "N to M" range tokens (values were already
988
- * engine-correct; the blocker was harness-side). */
989
- run_test_set("fn/distinct-values.xml", {}, 98, {},
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.255",
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",
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.255.1
4
+ version: 1.9.260.0
5
5
  platform: arm-linux
6
6
  authors:
7
7
  - Ribose Inc.