carray 3.0.6 → 3.0.7

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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +24 -3
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_group.c +6 -6
  6. data/ext/ca_binop_dispatch.c +8 -0
  7. data/ext/ca_binop_dispatch.h +1 -0
  8. data/ext/ca_categorical_iterator.c +7 -7
  9. data/ext/ca_kernel_iterator.h +6 -4
  10. data/ext/ca_obj_array.c +4 -4
  11. data/ext/ca_obj_bincmp.c +19 -3
  12. data/ext/ca_obj_binop.c +36 -11
  13. data/ext/ca_obj_bitarray.c +2 -4
  14. data/ext/ca_obj_bitfield.c +86 -4
  15. data/ext/ca_obj_byte_swap.c +2 -4
  16. data/ext/ca_obj_const_string.c +422 -257
  17. data/ext/ca_obj_face.c +71 -33
  18. data/ext/ca_obj_fake.c +112 -13
  19. data/ext/ca_obj_farray.c +7 -1
  20. data/ext/ca_obj_fixlen_string.c +51 -17
  21. data/ext/ca_obj_grid.c +12 -7
  22. data/ext/ca_obj_meld.c +5 -5
  23. data/ext/ca_obj_monop.c +65 -39
  24. data/ext/ca_obj_object.c +9 -9
  25. data/ext/ca_obj_reduce.c +2 -4
  26. data/ext/ca_obj_refer.c +104 -10
  27. data/ext/ca_obj_repeat.c +1 -1
  28. data/ext/ca_obj_roll.c +2 -4
  29. data/ext/ca_obj_select.c +8 -6
  30. data/ext/ca_obj_select_axis.c +8 -6
  31. data/ext/ca_obj_stack.c +5 -5
  32. data/ext/ca_obj_stride.c +46 -8
  33. data/ext/ca_obj_string.c +2 -2
  34. data/ext/ca_obj_tile.c +2 -4
  35. data/ext/ca_obj_triop.c +15 -12
  36. data/ext/ca_obj_window.c +22 -15
  37. data/ext/ca_op_ipower.c +63 -15
  38. data/ext/ca_op_powi.h +6 -29
  39. data/ext/ca_sort_kernels.h +3 -0
  40. data/ext/ca_sweep_engine.c +90 -23
  41. data/ext/ca_sweep_engine.h +5 -6
  42. data/ext/caframe_csv_reader.c +1 -1
  43. data/ext/carray.h +3 -1
  44. data/ext/carray_access.c +98 -39
  45. data/ext/carray_address_basis.c +98 -24
  46. data/ext/carray_broadcast.c +2 -2
  47. data/ext/carray_call_cfunc.c +5 -8
  48. data/ext/carray_cast.c +311 -37
  49. data/ext/carray_cast_func.rb +37 -5
  50. data/ext/carray_conversion.c +56 -2
  51. data/ext/carray_copy.c +24 -0
  52. data/ext/carray_core.c +69 -6
  53. data/ext/carray_count.c +28 -9
  54. data/ext/carray_element.c +9 -3
  55. data/ext/carray_factorize.c +11 -11
  56. data/ext/carray_histogram.c +2 -2
  57. data/ext/carray_index_classifier.c +7 -5
  58. data/ext/carray_internal.h +94 -0
  59. data/ext/carray_kernels_binop.c +55 -55
  60. data/ext/carray_kernels_init.c +14 -14
  61. data/ext/carray_kernels_monop.c +90 -140
  62. data/ext/carray_lazy.c +16 -1
  63. data/ext/carray_loop.c +9 -2
  64. data/ext/carray_mask.c +118 -11
  65. data/ext/carray_median_percentile.c +13 -8
  66. data/ext/carray_memory_view.c +41 -22
  67. data/ext/carray_operator.c +72 -11
  68. data/ext/carray_order.c +52 -9
  69. data/ext/carray_partition.c +1 -1
  70. data/ext/carray_random.c +1 -1
  71. data/ext/carray_round.c +24 -1
  72. data/ext/carray_scatter.c +4 -0
  73. data/ext/carray_segment.c +1 -1
  74. data/ext/carray_slab.c +13 -4
  75. data/ext/carray_sort.c +6 -3
  76. data/ext/carray_sort_kernel.c +24 -0
  77. data/ext/carray_struct.c +21 -6
  78. data/ext/carray_test.c +85 -39
  79. data/ext/carray_time_text.c +6 -0
  80. data/ext/carray_utils.c +124 -54
  81. data/ext/mk_call_cfunc.rb +6 -11
  82. data/ext/mkkernel.rb +37 -27
  83. data/ext/version.h +4 -4
  84. data/lib/carray/axis_group.rb +9 -6
  85. data/lib/carray/basics.rb +26 -8
  86. data/lib/carray/bincount_nd.rb +8 -2
  87. data/lib/carray/block_iterator.rb +48 -21
  88. data/lib/carray/categorical.rb +21 -8
  89. data/lib/carray/categorical_iterator.rb +2 -1
  90. data/lib/carray/conditional.rb +52 -4
  91. data/lib/carray/const_string.rb +74 -0
  92. data/lib/carray/construct.rb +2 -2
  93. data/lib/carray/core_extensions.rb +19 -68
  94. data/lib/carray/data_type_extension.rb +1 -0
  95. data/lib/carray/fixlen_string.rb +83 -1
  96. data/lib/carray/frame/column_verbs.rb +61 -6
  97. data/lib/carray/frame/concat.rb +50 -6
  98. data/lib/carray/frame/csv_parser.rb +13 -4
  99. data/lib/carray/frame/frame.rb +38 -6
  100. data/lib/carray/frame/group.rb +67 -40
  101. data/lib/carray/frame/io.rb +10 -5
  102. data/lib/carray/frame/join.rb +20 -6
  103. data/lib/carray/frame/records.rb +6 -4
  104. data/lib/carray/frame/reshape.rb +25 -5
  105. data/lib/carray/frame/verbs.rb +78 -25
  106. data/lib/carray/frame/windows.rb +30 -6
  107. data/lib/carray/fuse_source.rb +4 -2
  108. data/lib/carray/iterator.rb +8 -0
  109. data/lib/carray/key_windows.rb +10 -4
  110. data/lib/carray/lazy.rb +12 -17
  111. data/lib/carray/methods/bin.rb +3 -0
  112. data/lib/carray/methods/composition.rb +1 -1
  113. data/lib/carray/methods/factorize.rb +2 -0
  114. data/lib/carray/methods/gather_nd.rb +4 -1
  115. data/lib/carray/methods/is_in.rb +23 -3
  116. data/lib/carray/methods/locate_addr.rb +63 -12
  117. data/lib/carray/methods/repeat.rb +11 -1
  118. data/lib/carray/record_order.rb +35 -6
  119. data/lib/carray/runtime.rb +10 -0
  120. data/lib/carray/segment_iterator.rb +19 -16
  121. data/lib/carray/serialize.rb +22 -0
  122. data/lib/carray/slab_iterator.rb +1 -1
  123. data/lib/carray/stack.rb +3 -3
  124. data/lib/carray/string.rb +6 -6
  125. data/lib/carray/string_operation_extension.rb +25 -4
  126. data/lib/carray/struct_builder.rb +4 -1
  127. data/lib/carray/time.rb +190 -46
  128. data/lib/carray/window_iterator.rb +48 -12
  129. data/yard-stubs/ca_obj_bitfield.rb +4 -1
  130. data/yard-stubs/ca_obj_refer.rb +3 -0
  131. data/yard-stubs/ca_obj_window.rb +2 -1
  132. data/yard-stubs/carray_access.rb +4 -2
  133. data/yard-stubs/carray_cast.rb +6 -0
  134. data/yard-stubs/carray_count.rb +6 -3
  135. data/yard-stubs/carray_math.rb +5 -0
  136. data/yard-stubs/carray_order.rb +6 -0
  137. data/yard-stubs/carray_scatter.rb +2 -1
  138. data/yard-stubs/carray_test.rb +18 -5
  139. metadata +2 -2
@@ -372,7 +372,7 @@ rb_ca_const_string_wrap_method (int argc, VALUE *argv, VALUE klass)
372
372
  per-element Ruby build loop -- no `String#b` copy, no `pack`, no per-row
373
373
  method dispatch. `renc` is the target Encoding (nil = UTF-8). */
374
374
  static VALUE
375
- rb_ca_const_string_build (VALUE klass, VALUE values, VALUE renc)
375
+ rb_ca_const_string_build (VALUE klass, volatile VALUE values, VALUE renc)
376
376
  {
377
377
  long i, n;
378
378
  int encoding_id;
@@ -385,6 +385,8 @@ rb_ca_const_string_build (VALUE klass, VALUE values, VALUE renc)
385
385
  long hint = 0;
386
386
 
387
387
  Check_Type(values, T_ARRAY);
388
+ /* A to_s below can change the caller's Array; read a copy of it. */
389
+ values = rb_ary_dup(values);
388
390
  n = RARRAY_LEN(values);
389
391
  encoding_id = NIL_P(renc) ? rb_utf8_encindex() : rb_to_encoding_index(renc);
390
392
  enc = rb_enc_from_index(encoding_id);
@@ -403,7 +405,7 @@ rb_ca_const_string_build (VALUE klass, VALUE values, VALUE renc)
403
405
  pair = (int64_t *) pe->ptr;
404
406
 
405
407
  for ( i = 0; i < n; i++ ) {
406
- VALUE s = RARRAY_AREF(values, i);
408
+ volatile VALUE s = RARRAY_AREF(values, i);
407
409
  if ( NIL_P(s) ) {
408
410
  if ( ! mask ) {
409
411
  ca_create_mask(pe);
@@ -506,6 +508,76 @@ rb_ca_const_string_to_string (VALUE self)
506
508
  rb_intern("wrap"), 1, out);
507
509
  }
508
510
 
511
+ /* A scan reads the (start,end) pairs and the bytes of a column. */
512
+ typedef struct {
513
+ CAConstString *ca;
514
+ int64_t *off;
515
+ boolean8_t *m;
516
+ const char *buf;
517
+ long buflen;
518
+ ca_size_t n;
519
+ } ca_const_string_scan_t;
520
+
521
+ /* What a scan's body runs with the window open. s holds one scan, or two
522
+ for an operation on a pair of arrays. */
523
+ typedef VALUE (*ca_const_string_scan_body_t) (ca_const_string_scan_t *s, void *arg);
524
+
525
+ typedef struct {
526
+ int32_t n;
527
+ ca_const_string_scan_t *s;
528
+ ca_const_string_scan_body_t body;
529
+ void *arg;
530
+ } ca_const_string_run_t;
531
+
532
+ static VALUE
533
+ ca_const_string_run_body (VALUE varg)
534
+ {
535
+ ca_const_string_run_t *r = (ca_const_string_run_t *) varg;
536
+ int32_t k;
537
+ for ( k = 0; k < r->n; k++ ) {
538
+ ca_const_string_scan_t *s = &r->s[k];
539
+ s->off = (int64_t *) s->ca->ptr;
540
+ s->m = s->ca->mask ? (boolean8_t *) s->ca->mask->ptr : NULL;
541
+ s->buf = RSTRING_PTR(s->ca->buffer);
542
+ s->buflen = RSTRING_LEN(s->ca->buffer);
543
+ s->n = s->ca->elements;
544
+ }
545
+ return r->body(r->s, r->arg);
546
+ }
547
+
548
+ /* Runs body(s, arg) with the offsets of the n (1 or 2) columns in cols
549
+ attached, s[k] scanning cols[k], and closes the window however body
550
+ leaves: what body allocates can fail, and body may raise. */
551
+ static VALUE
552
+ ca_const_string_scan_cols (int32_t n, const VALUE *cols,
553
+ ca_const_string_scan_t *s,
554
+ ca_const_string_scan_body_t body, void *arg)
555
+ {
556
+ ca_const_string_run_t r;
557
+ CArray *list[2];
558
+ int32_t k;
559
+
560
+ r.n = n;
561
+ r.s = s;
562
+ r.body = body;
563
+ r.arg = arg;
564
+ for ( k = 0; k < n; k++ ) {
565
+ TypedData_Get_Struct(cols[k], CAConstString, &catext_data_type, s[k].ca);
566
+ ca_update_mask((CArray *) s[k].ca);
567
+ list[k] = (CArray *) s[k].ca;
568
+ }
569
+ return ca_attach_window(n, list, n == 2 ? "rr" : "r",
570
+ ca_const_string_run_body, (VALUE) &r);
571
+ }
572
+
573
+ /* The scan of one column. */
574
+ static VALUE
575
+ ca_const_string_scan_run (VALUE self, ca_const_string_scan_t *s,
576
+ ca_const_string_scan_body_t body, void *arg)
577
+ {
578
+ return ca_const_string_scan_cols(1, &self, s, body, arg);
579
+ }
580
+
509
581
  /* copy = compacting deep copy — the only chain-descent point in
510
582
  CAConstString. Produces a standalone CAConstString:
511
583
  - a fresh contiguous fixlen-16 (start,end) pair entity
@@ -515,73 +587,76 @@ rb_ca_const_string_to_string (VALUE self)
515
587
  - fully detached from the original buffer / pair-source
516
588
  Cost is O(live bytes); a near-full copy is a near-full rebuild (accepted).
517
589
  Done at the byte level (no per-element Ruby String boxing). */
590
+ typedef struct {
591
+ volatile VALUE pairs; /* the new (start,end) pair entity */
592
+ volatile VALUE buffer; /* the compacted bytes */
593
+ } ca_const_string_copy_t;
594
+
595
+ /* Repacks each live record's bytes into a new buffer in the view's order,
596
+ writing the new pairs and carrying the mask. */
518
597
  static VALUE
519
- rb_ca_const_string_copy (VALUE self)
598
+ ca_const_string_copy_body (ca_const_string_scan_t *scan, void *arg)
520
599
  {
521
- volatile VALUE vout, newbuf;
522
- CAConstString *ca;
600
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
601
+ ca_const_string_copy_t *c = (ca_const_string_copy_t *) arg;
523
602
  CArray *co;
524
- ca_size_t i, n;
525
- int64_t *src_pair, *dst_pair;
526
- boolean8_t *m = NULL;
527
- const char *oldbuf;
528
- long oldbuflen;
529
-
530
- TypedData_Get_Struct(self, CAConstString, &catext_data_type, ca);
531
-
532
- /* materialize logical-order (start,end) pairs + mask */
533
- ca_update_mask((CArray *) ca);
534
- ca_attach((CArray *) ca); /* window: detached before its one raise */
535
-
536
- n = ca->elements;
537
- src_pair = (int64_t *) ca->ptr;
538
- m = ca->mask ? (boolean8_t *) ca->mask->ptr : NULL;
539
- oldbuf = RSTRING_PTR(ca->buffer);
540
- oldbuflen = RSTRING_LEN(ca->buffer);
603
+ ca_size_t i;
604
+ int64_t *dst_pair;
541
605
 
542
606
  /* fresh fixlen-16 pair entity, same shape (N-D preserved) */
543
- vout = rb_carray_new(CA_FIXLEN, ca->ndim, ca->dim, 2 * sizeof(int64_t), NULL);
544
- TypedData_Get_Struct(vout, CArray, &carray_data_type, co);
607
+ c->pairs = rb_carray_new(CA_FIXLEN, s->ca->ndim, s->ca->dim,
608
+ 2 * sizeof(int64_t), NULL);
609
+ TypedData_Get_Struct(c->pairs, CArray, &carray_data_type, co);
545
610
  dst_pair = (int64_t *) co->ptr;
546
611
 
547
612
  /* growable compacted buffer (BINARY) */
548
- newbuf = rb_str_buf_new(oldbuflen);
549
- rb_enc_associate_index(newbuf, rb_ascii8bit_encindex());
613
+ c->buffer = rb_str_buf_new(s->buflen);
614
+ rb_enc_associate_index(c->buffer, rb_ascii8bit_encindex());
550
615
 
551
- for ( i = 0; i < n; i++ ) {
616
+ for ( i = 0; i < s->n; i++ ) {
552
617
  int64_t start, end, len;
553
- if ( m && m[i] ) {
618
+ if ( s->m && s->m[i] ) {
554
619
  dst_pair[2 * i] = 0;
555
620
  dst_pair[2 * i + 1] = 0;
556
621
  continue;
557
622
  }
558
- start = src_pair[2 * i];
559
- end = src_pair[2 * i + 1];
623
+ start = s->off[2 * i];
624
+ end = s->off[2 * i + 1];
560
625
  len = end - start;
561
- if ( start < 0 || len < 0 || end > (int64_t) oldbuflen ) {
562
- ca_detach((CArray *) ca);
626
+ if ( start < 0 || len < 0 || end > (int64_t) s->buflen ) {
563
627
  rb_raise(rb_eIndexError,
564
628
  "CAConstString#copy: record [%lld,%lld) out of buffer range",
565
629
  (long long) start, (long long) end);
566
630
  }
567
- dst_pair[2 * i] = (int64_t) RSTRING_LEN(newbuf);
568
- rb_str_cat(newbuf, oldbuf + start, (long) len);
569
- dst_pair[2 * i + 1] = (int64_t) RSTRING_LEN(newbuf);
631
+ dst_pair[2 * i] = (int64_t) RSTRING_LEN(c->buffer);
632
+ rb_str_cat(c->buffer, s->buf + start, (long) len);
633
+ dst_pair[2 * i + 1] = (int64_t) RSTRING_LEN(c->buffer);
570
634
  }
571
635
 
572
636
  /* carry mask onto the new pair entity */
573
- if ( m ) {
637
+ if ( s->m ) {
574
638
  boolean8_t *nm;
575
639
  ca_create_mask(co);
576
640
  nm = (boolean8_t *) co->mask->ptr;
577
- for ( i = 0; i < n; i++ ) {
578
- nm[i] = m[i] ? 1 : 0;
641
+ for ( i = 0; i < s->n; i++ ) {
642
+ nm[i] = s->m[i] ? 1 : 0;
579
643
  }
580
644
  }
645
+ return Qnil;
646
+ }
581
647
 
582
- ca_detach((CArray *) ca);
583
-
584
- vout = rb_ca_const_string_wrap(vout, rb_str_freeze(newbuf), ca->encoding_id);
648
+ static VALUE
649
+ rb_ca_const_string_copy (VALUE self)
650
+ {
651
+ ca_const_string_scan_t s;
652
+ ca_const_string_copy_t c;
653
+ volatile VALUE vout;
654
+
655
+ c.pairs = Qnil;
656
+ c.buffer = Qnil;
657
+ ca_const_string_scan_run(self, &s, ca_const_string_copy_body, &c);
658
+ vout = rb_ca_const_string_wrap(c.pairs, rb_str_freeze(c.buffer),
659
+ s.ca->encoding_id);
585
660
  rb_ca_inherit_attr(vout, self);
586
661
  return vout;
587
662
  }
@@ -604,67 +679,6 @@ rb_ca_const_string_to_ca (int argc, VALUE *argv, VALUE self)
604
679
  the layer where CAConstString outperforms `to_object` followed by
605
680
  per-cell Ruby String work. */
606
681
 
607
- typedef struct {
608
- CAConstString *ca;
609
- int64_t *off;
610
- boolean8_t *m;
611
- const char *buf;
612
- long buflen;
613
- ca_size_t n;
614
- } ca_const_string_scan_t;
615
-
616
- /* Opens the scan's window; ca_const_string_scan_end closes it. What runs
617
- between them in this file reads bytes and raises only after closing. */
618
- static void
619
- ca_const_string_scan_begin (VALUE self, ca_const_string_scan_t *s)
620
- {
621
- TypedData_Get_Struct(self, CAConstString, &catext_data_type, s->ca);
622
- ca_update_mask((CArray *) s->ca);
623
- ca_attach((CArray *) s->ca); /* window: closed by ca_const_string_scan_end */
624
- s->off = (int64_t *) s->ca->ptr;
625
- s->m = s->ca->mask ? (boolean8_t *) s->ca->mask->ptr : NULL;
626
- s->buf = RSTRING_PTR(s->ca->buffer);
627
- s->buflen = RSTRING_LEN(s->ca->buffer);
628
- s->n = s->ca->elements;
629
- }
630
-
631
- static void
632
- ca_const_string_scan_end (ca_const_string_scan_t *s)
633
- {
634
- ca_detach((CArray *) s->ca);
635
- }
636
-
637
- typedef struct {
638
- VALUE self;
639
- ca_const_string_scan_t *s;
640
- } ca_const_string_scan_arg_t;
641
-
642
- static VALUE
643
- ca_const_string_scan_begin_body (VALUE arg)
644
- {
645
- ca_const_string_scan_arg_t *x = (ca_const_string_scan_arg_t *) arg;
646
- ca_const_string_scan_begin(x->self, x->s);
647
- return Qnil;
648
- }
649
-
650
- /* Begins the scans of two arrays, both or neither: when the second one's
651
- attach raises, the first is closed before the raise goes on. */
652
- static void
653
- ca_const_string_scan_begin_pair (VALUE va, ca_const_string_scan_t *a,
654
- VALUE vb, ca_const_string_scan_t *b)
655
- {
656
- ca_const_string_scan_arg_t x;
657
- int tag = 0;
658
- ca_const_string_scan_begin(va, a);
659
- x.self = vb;
660
- x.s = b;
661
- /* protect: ends the first scan, which is not an array */
662
- rb_protect(ca_const_string_scan_begin_body, (VALUE) &x, &tag);
663
- if ( tag ) {
664
- ca_const_string_scan_end(a);
665
- rb_jump_tag(tag);
666
- }
667
- }
668
682
 
669
683
  /* Resolve element i to its record bytes; returns 0 if masked.
670
684
  Storage is (start,end) int64 pairs: s->off holds 2 int64 per element,
@@ -720,65 +734,72 @@ ca_const_string_memmem (const char *hay, long hlen, const char *need, long nlen)
720
734
  return NULL;
721
735
  }
722
736
 
723
- /* CAConstString#byte_length → int64 CArray of per-element byte lengths. */
724
737
  static VALUE
725
- rb_ca_const_string_byte_length (VALUE self)
738
+ ca_const_string_byte_length_body (ca_const_string_scan_t *scan, void *arg)
726
739
  {
727
- ca_const_string_scan_t s;
728
- VALUE vout;
740
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
741
+ volatile VALUE vout;
729
742
  CArray *co;
730
743
  int64_t *op;
731
744
  const char *p;
732
745
  int64_t len;
733
746
  ca_size_t i;
734
747
 
735
- ca_const_string_scan_begin(self, &s);
736
- vout = rb_carray_new(CA_INT64, s.ca->ndim, s.ca->dim, 0, NULL);
748
+ vout = rb_carray_new(CA_INT64, s->ca->ndim, s->ca->dim, 0, NULL);
737
749
  TypedData_Get_Struct(vout, CArray, &carray_data_type, co);
738
750
  op = (int64_t *) co->ptr;
739
- for ( i = 0; i < s.n; i++ ) {
740
- op[i] = ca_const_string_record(&s, i, &p, &len) ? (int64_t) len : 0;
751
+ for ( i = 0; i < s->n; i++ ) {
752
+ op[i] = ca_const_string_record(s, i, &p, &len) ? (int64_t) len : 0;
741
753
  }
742
- if ( s.m ) {
754
+ if ( s->m ) {
743
755
  boolean8_t *nm;
744
756
  ca_create_mask(co);
745
757
  nm = (boolean8_t *) co->mask->ptr;
746
- for ( i = 0; i < s.n; i++ ) {
747
- nm[i] = s.m[i] ? 1 : 0;
758
+ for ( i = 0; i < s->n; i++ ) {
759
+ nm[i] = s->m[i] ? 1 : 0;
748
760
  }
749
761
  }
750
- ca_const_string_scan_end(&s);
751
762
  return vout;
752
763
  }
753
764
 
765
+ /* CAConstString#byte_length → int64 CArray of per-element byte lengths. */
766
+ static VALUE
767
+ rb_ca_const_string_byte_length (VALUE self)
768
+ {
769
+ ca_const_string_scan_t s;
770
+ return ca_const_string_scan_run(self, &s,
771
+ ca_const_string_byte_length_body, NULL);
772
+ }
773
+
754
774
  /* op codes for the shared predicate scan. */
755
775
  enum { CA_TEXT_EQ, CA_TEXT_START, CA_TEXT_END, CA_TEXT_INCLUDE };
756
776
 
757
777
  /* CAConstString#eq / start_with? / end_with? / include? — boolean mask vs a scalar
758
778
  String query, byte-native. */
779
+ typedef struct {
780
+ const char *qp;
781
+ long qlen;
782
+ int op;
783
+ } ca_const_string_query_t;
784
+
759
785
  static VALUE
760
- ca_const_string_predicate (VALUE self, VALUE query, int op)
786
+ ca_const_string_predicate_body (ca_const_string_scan_t *scan, void *arg)
761
787
  {
762
- ca_const_string_scan_t s;
763
- VALUE vout;
788
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
789
+ ca_const_string_query_t *q = (ca_const_string_query_t *) arg;
790
+ const char *qp = q->qp, *p;
791
+ long qlen = q->qlen;
792
+ int op = q->op;
793
+ volatile VALUE vout;
764
794
  boolean8_t *out;
765
- const char *qp, *p;
766
- long qlen;
767
795
  int64_t len;
768
796
  ca_size_t i;
769
797
 
770
- if ( TYPE(query) != T_STRING ) {
771
- query = rb_obj_as_string(query);
772
- }
773
- qp = RSTRING_PTR(query);
774
- qlen = RSTRING_LEN(query);
775
-
776
- ca_const_string_scan_begin(self, &s);
777
- vout = ca_const_string_bool_out(&s, &out);
798
+ vout = ca_const_string_bool_out(scan, &out);
778
799
 
779
- for ( i = 0; i < s.n; i++ ) {
800
+ for ( i = 0; i < s->n; i++ ) {
780
801
  boolean8_t r = 0;
781
- if ( ca_const_string_record(&s, i, &p, &len) ) {
802
+ if ( ca_const_string_record(s, i, &p, &len) ) {
782
803
  switch ( op ) {
783
804
  case CA_TEXT_EQ:
784
805
  r = ( len == qlen && memcmp(p, qp, qlen) == 0 );
@@ -796,13 +817,91 @@ ca_const_string_predicate (VALUE self, VALUE query, int op)
796
817
  }
797
818
  out[i] = r;
798
819
  }
799
- ca_const_string_scan_end(&s);
800
820
  return vout;
801
821
  }
802
822
 
803
- static VALUE rb_ca_const_string_start_with (VALUE self, VALUE q) { return ca_const_string_predicate(self, q, CA_TEXT_START); }
804
- static VALUE rb_ca_const_string_end_with (VALUE self, VALUE q) { return ca_const_string_predicate(self, q, CA_TEXT_END); }
805
- static VALUE rb_ca_const_string_include (VALUE self, VALUE q) { return ca_const_string_predicate(self, q, CA_TEXT_INCLUDE); }
823
+ static VALUE
824
+ ca_const_string_predicate (VALUE self, VALUE query, int op)
825
+ {
826
+ ca_const_string_scan_t s;
827
+ ca_const_string_query_t q;
828
+ volatile VALUE vquery = query;
829
+
830
+ if ( TYPE(vquery) != T_STRING ) {
831
+ vquery = rb_obj_as_string(vquery);
832
+ }
833
+ q.qp = RSTRING_PTR(vquery);
834
+ q.qlen = RSTRING_LEN(vquery);
835
+ q.op = op;
836
+ {
837
+ VALUE out = ca_const_string_scan_run(self, &s,
838
+ ca_const_string_predicate_body, &q);
839
+ RB_GC_GUARD(vquery);
840
+ return out;
841
+ }
842
+ }
843
+
844
+ /* The native scans read one String. Anything else (a Regexp, several
845
+ prefixes, a value that is no String) is asked of the Strings themselves. */
846
+ static VALUE
847
+ ca_const_string_ask_strings (VALUE self, const char *name, int argc, VALUE *argv)
848
+ {
849
+ return rb_funcallv(rb_funcall(self, rb_intern("to_string"), 0),
850
+ rb_intern(name), argc, argv);
851
+ }
852
+
853
+ static VALUE
854
+ rb_ca_const_string_start_with (int argc, VALUE *argv, VALUE self)
855
+ {
856
+ if ( argc == 1 && RB_TYPE_P(argv[0], T_STRING) ) {
857
+ return ca_const_string_predicate(self, argv[0], CA_TEXT_START);
858
+ }
859
+ return ca_const_string_ask_strings(self, "start_with?", argc, argv);
860
+ }
861
+
862
+ static VALUE
863
+ rb_ca_const_string_end_with (int argc, VALUE *argv, VALUE self)
864
+ {
865
+ if ( argc == 1 && RB_TYPE_P(argv[0], T_STRING) ) {
866
+ return ca_const_string_predicate(self, argv[0], CA_TEXT_END);
867
+ }
868
+ return ca_const_string_ask_strings(self, "end_with?", argc, argv);
869
+ }
870
+
871
+ static VALUE
872
+ rb_ca_const_string_include (VALUE self, VALUE q)
873
+ {
874
+ if ( RB_TYPE_P(q, T_STRING) ) {
875
+ return ca_const_string_predicate(self, q, CA_TEXT_INCLUDE);
876
+ }
877
+ return ca_const_string_ask_strings(self, "include?", 1, &q);
878
+ }
879
+
880
+ static VALUE
881
+ ca_const_string_eq_pair_body (ca_const_string_scan_t *scan, void *arg)
882
+ {
883
+ ca_const_string_scan_t a0 = scan[0], b0 = scan[1], *a = &a0, *b = &b0; /* local copies: the writes below cannot alias them */
884
+ volatile VALUE vout;
885
+ boolean8_t *out;
886
+ /* record() leaves these unset for masked cells; short-circuit eval means
887
+ they are read only when both records are valid, but GCC cannot prove
888
+ that -- initialize to keep -Wmaybe-uninitialized quiet and defensive. */
889
+ const char *pa = NULL, *pb = NULL;
890
+ int64_t la = 0, lb = 0;
891
+ ca_size_t i;
892
+
893
+ if ( a->n != b->n ) {
894
+ rb_raise(rb_eArgError, "CAConstString#eq: element count mismatch (%lld vs %lld)",
895
+ (long long) a->n, (long long) b->n);
896
+ }
897
+ vout = ca_const_string_bool_out(&scan[0], &out); /* mask carried from self */
898
+ for ( i = 0; i < a->n; i++ ) {
899
+ int va = ca_const_string_record(a, i, &pa, &la);
900
+ int vb = ca_const_string_record(b, i, &pb, &lb);
901
+ out[i] = ( va && vb && la == lb && memcmp(pa, pb, la) == 0 );
902
+ }
903
+ return vout;
904
+ }
806
905
 
807
906
  /* CAConstString#eq(value): value is a scalar String (broadcast) or a CAConstString
808
907
  (element-wise). Returns a boolean CArray. */
@@ -810,32 +909,12 @@ static VALUE
810
909
  rb_ca_const_string_eq (VALUE self, VALUE other)
811
910
  {
812
911
  if ( rb_obj_is_kind_of(other, rb_cCAConstString) ) {
813
- ca_const_string_scan_t a, b;
814
- VALUE vout;
815
- boolean8_t *out;
816
- /* record() leaves these unset for masked cells; short-circuit eval means
817
- they are read only when both records are valid, but GCC cannot prove
818
- that -- initialize to keep -Wmaybe-uninitialized quiet and defensive. */
819
- const char *pa = NULL, *pb = NULL;
820
- int64_t la = 0, lb = 0;
821
- ca_size_t i;
822
-
823
- ca_const_string_scan_begin_pair(self, &a, other, &b);
824
- if ( a.n != b.n ) {
825
- ca_const_string_scan_end(&a);
826
- ca_const_string_scan_end(&b);
827
- rb_raise(rb_eArgError, "CAConstString#eq: element count mismatch (%lld vs %lld)",
828
- (long long) a.n, (long long) b.n);
829
- }
830
- vout = ca_const_string_bool_out(&a, &out); /* mask carried from self */
831
- for ( i = 0; i < a.n; i++ ) {
832
- int va = ca_const_string_record(&a, i, &pa, &la);
833
- int vb = ca_const_string_record(&b, i, &pb, &lb);
834
- out[i] = ( va && vb && la == lb && memcmp(pa, pb, la) == 0 );
835
- }
836
- ca_const_string_scan_end(&a);
837
- ca_const_string_scan_end(&b);
838
- return vout;
912
+ ca_const_string_scan_t s[2];
913
+ VALUE cols[2];
914
+ cols[0] = self;
915
+ cols[1] = other;
916
+ return ca_const_string_scan_cols(2, cols, s,
917
+ ca_const_string_eq_pair_body, NULL);
839
918
  }
840
919
  /* Another array is compared cell by cell as Strings, where a CAString
841
920
  reconciles it (an object array of Strings, a fixlen or a CAString). */
@@ -843,6 +922,9 @@ rb_ca_const_string_eq (VALUE self, VALUE other)
843
922
  return rb_funcall(rb_ca_const_string_to_string(self), rb_intern("eq"), 1,
844
923
  other);
845
924
  }
925
+ if ( ! RB_TYPE_P(other, T_STRING) ) {
926
+ return ca_const_string_ask_strings(self, "eq", 1, &other);
927
+ }
846
928
  return ca_const_string_predicate(self, other, CA_TEXT_EQ);
847
929
  }
848
930
 
@@ -850,32 +932,78 @@ rb_ca_const_string_eq (VALUE self, VALUE other)
850
932
  value. With no value, the number of non-masked elements, as CArray#count
851
933
  answers. */
852
934
  static VALUE
853
- rb_ca_const_string_count (int argc, VALUE *argv, VALUE self)
935
+ ca_const_string_count_body (ca_const_string_scan_t *scan, void *arg)
854
936
  {
855
- ca_const_string_scan_t s;
856
- const char *qp, *p;
857
- long qlen;
937
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
938
+ ca_const_string_query_t *q = (ca_const_string_query_t *) arg;
939
+ const char *p;
858
940
  int64_t len;
859
941
  ca_size_t i, c = 0;
860
- VALUE query;
942
+ for ( i = 0; i < s->n; i++ ) {
943
+ if ( ca_const_string_record(s, i, &p, &len) && len == q->qlen &&
944
+ memcmp(p, q->qp, q->qlen) == 0 ) {
945
+ c++;
946
+ }
947
+ }
948
+ return SIZET2NUM(c);
949
+ }
861
950
 
862
- if ( rb_scan_args(argc, argv, "01", &query) == 0 ) {
863
- return rb_funcall(self, rb_intern("count_not_masked"), 0);
951
+ /* The first (non-masked) cell byte-equal to the query, as a flat index, or
952
+ nil when none matches. */
953
+ static VALUE
954
+ ca_const_string_search_body (ca_const_string_scan_t *scan, void *arg)
955
+ {
956
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
957
+ ca_const_string_query_t *q = (ca_const_string_query_t *) arg;
958
+ const char *p;
959
+ int64_t len;
960
+ ca_size_t i;
961
+ for ( i = 0; i < s->n; i++ ) {
962
+ if ( ca_const_string_record(s, i, &p, &len) && len == q->qlen &&
963
+ memcmp(p, q->qp, (size_t) q->qlen) == 0 ) {
964
+ return SIZET2NUM(i);
965
+ }
864
966
  }
865
- if ( TYPE(query) != T_STRING ) {
866
- query = rb_obj_as_string(query);
967
+ return Qnil;
968
+ }
969
+
970
+ /* Runs a scan that compares each cell with a query String. */
971
+ static VALUE
972
+ ca_const_string_query_run (VALUE self, VALUE query,
973
+ ca_const_string_scan_body_t body)
974
+ {
975
+ ca_const_string_scan_t s;
976
+ ca_const_string_query_t q;
977
+ volatile VALUE vquery = query;
978
+ VALUE out;
979
+
980
+ if ( TYPE(vquery) != T_STRING ) {
981
+ vquery = rb_obj_as_string(vquery);
867
982
  }
868
- qp = RSTRING_PTR(query);
869
- qlen = RSTRING_LEN(query);
983
+ q.qp = RSTRING_PTR(vquery);
984
+ q.qlen = RSTRING_LEN(vquery);
985
+ q.op = CA_TEXT_EQ;
986
+ out = ca_const_string_scan_run(self, &s, body, &q);
987
+ RB_GC_GUARD(vquery);
988
+ return out;
989
+ }
870
990
 
871
- ca_const_string_scan_begin(self, &s);
872
- for ( i = 0; i < s.n; i++ ) {
873
- if ( ca_const_string_record(&s, i, &p, &len) && len == qlen && memcmp(p, qp, qlen) == 0 ) {
874
- c++;
875
- }
991
+ static VALUE
992
+ rb_ca_const_string_count (int argc, VALUE *argv, VALUE self)
993
+ {
994
+ VALUE query;
995
+ if ( rb_keyword_given_p() ) {
996
+ /* axis: and the other reduction keywords: the scan below is flat */
997
+ return rb_funcallv_kw(rb_funcall(self, rb_intern("to_string"), 0),
998
+ rb_intern("count"), argc, argv, RB_PASS_KEYWORDS);
876
999
  }
877
- ca_const_string_scan_end(&s);
878
- return SIZET2NUM(c);
1000
+ if ( rb_scan_args(argc, argv, "01", &query) == 0 ) {
1001
+ return rb_funcall(self, rb_intern("count_not_masked"), 0);
1002
+ }
1003
+ if ( ! RB_TYPE_P(query, T_STRING) ) {
1004
+ return ca_const_string_ask_strings(self, "count", 1, &query);
1005
+ }
1006
+ return ca_const_string_query_run(self, query, ca_const_string_count_body);
879
1007
  }
880
1008
 
881
1009
  /* CAConstString#search(query) / #find_value_index(query): flat index of the
@@ -884,30 +1012,25 @@ rb_ca_const_string_count (int argc, VALUE *argv, VALUE self)
884
1012
  offset entity, not the string content), so this is a native memcmp scan --
885
1013
  the exact-lookup counterpart of the native sort / min / max below. */
886
1014
  static VALUE
887
- rb_ca_const_string_search (VALUE self, VALUE query)
1015
+ rb_ca_const_string_find_value_index (VALUE self, VALUE query)
888
1016
  {
889
- ca_const_string_scan_t s;
890
- const char *qp, *p;
891
- long qlen;
892
- int64_t len;
893
- ca_size_t i;
894
- VALUE result = Qnil;
1017
+ if ( ! RB_TYPE_P(query, T_STRING) ) {
1018
+ return Qnil;
1019
+ }
1020
+ return ca_const_string_query_run(self, query, ca_const_string_search_body);
1021
+ }
895
1022
 
896
- if ( TYPE(query) != T_STRING ) {
897
- query = rb_obj_as_string(query);
1023
+ static VALUE
1024
+ rb_ca_const_string_search (int argc, VALUE *argv, VALUE self)
1025
+ {
1026
+ if ( rb_keyword_given_p() ) {
1027
+ return rb_funcallv_kw(rb_funcall(self, rb_intern("to_string"), 0),
1028
+ rb_intern("search"), argc, argv, RB_PASS_KEYWORDS);
898
1029
  }
899
- qp = RSTRING_PTR(query);
900
- qlen = RSTRING_LEN(query);
901
-
902
- ca_const_string_scan_begin(self, &s);
903
- for ( i = 0; i < s.n; i++ ) {
904
- if ( ca_const_string_record(&s, i, &p, &len) && len == qlen && memcmp(p, qp, (size_t) qlen) == 0 ) {
905
- result = SIZET2NUM(i);
906
- break;
907
- }
1030
+ if ( argc == 1 && RB_TYPE_P(argv[0], T_STRING) ) {
1031
+ return rb_ca_const_string_find_value_index(self, argv[0]);
908
1032
  }
909
- ca_const_string_scan_end(&s);
910
- return result;
1033
+ return ca_const_string_ask_strings(self, "search", argc, argv);
911
1034
  }
912
1035
 
913
1036
  /* ------------------------------------------------------------------- */
@@ -944,18 +1067,20 @@ ca_const_string_index_cmp (const void *a, const void *b)
944
1067
  return (ia > ib) - (ia < ib);
945
1068
  }
946
1069
 
947
- /* compute the ascending sort permutation; raises on masked input (matches
948
- base CArray#sort). caller xfree()s the returned index array. */
949
- static ca_size_t *
950
- ca_const_string_sort_perm (ca_const_string_scan_t *s)
1070
+ /* raises on masked input (matches base CArray#sort) */
1071
+ static void
1072
+ ca_const_string_refuse_masked_sort (ca_const_string_scan_t *s)
951
1073
  {
952
- ca_size_t *idx;
953
- ca_size_t i;
954
1074
  if ( s->m ) {
955
- ca_const_string_scan_end(s);
956
1075
  rb_raise(rb_eArgError, "CAConstString#sort: cannot sort an array with masked elements");
957
1076
  }
958
- idx = ALLOC_N(ca_size_t, s->n);
1077
+ }
1078
+
1079
+ /* computes the ascending sort permutation into idx[0..n-1] */
1080
+ static void
1081
+ ca_const_string_sort_perm (ca_const_string_scan_t *s, ca_size_t *idx)
1082
+ {
1083
+ ca_size_t i;
959
1084
  for ( i = 0; i < s->n; i++ ) idx[i] = i;
960
1085
  ruby_qsort(idx, (size_t) s->n, sizeof(ca_size_t), ca_const_string_byte_cmp, s);
961
1086
  /* qsort is not stable. Equal strings keep their order, as CArray's own
@@ -970,31 +1095,38 @@ ca_const_string_sort_perm (ca_const_string_scan_t *s)
970
1095
  }
971
1096
  i = j;
972
1097
  }
973
- return idx;
974
1098
  }
975
1099
 
976
1100
  /* CAConstString#sort_index → int64 CArray of the ascending sort permutation. */
977
1101
  static VALUE
978
- rb_ca_const_string_sort_index (VALUE self)
1102
+ ca_const_string_sort_index_body (ca_const_string_scan_t *s, void *arg)
979
1103
  {
980
- ca_const_string_scan_t s;
1104
+ volatile VALUE holder = 0; /* 0, not Qnil: ALLOCV_END reads a non-zero holder */
1105
+ volatile VALUE vout;
981
1106
  ca_size_t *idx;
982
- VALUE vout;
983
1107
  CArray *co;
984
1108
  int64_t *op;
985
1109
  ca_size_t i;
986
1110
 
987
- ca_const_string_scan_begin(self, &s);
988
- idx = ca_const_string_sort_perm(&s);
989
- vout = rb_carray_new(CA_INT64, s.ca->ndim, s.ca->dim, 0, NULL);
1111
+ ca_const_string_refuse_masked_sort(s);
1112
+ idx = ALLOCV_N(ca_size_t, holder, s->n > 0 ? s->n : 1);
1113
+ ca_const_string_sort_perm(s, idx);
1114
+ vout = rb_carray_new(CA_INT64, s->ca->ndim, s->ca->dim, 0, NULL);
990
1115
  TypedData_Get_Struct(vout, CArray, &carray_data_type, co);
991
1116
  op = (int64_t *) co->ptr;
992
- for ( i = 0; i < s.n; i++ ) op[i] = (int64_t) idx[i];
993
- xfree(idx);
994
- ca_const_string_scan_end(&s);
1117
+ for ( i = 0; i < s->n; i++ ) op[i] = (int64_t) idx[i];
1118
+ ALLOCV_END(holder);
995
1119
  return vout;
996
1120
  }
997
1121
 
1122
+ static VALUE
1123
+ rb_ca_const_string_sort_index (VALUE self)
1124
+ {
1125
+ ca_const_string_scan_t s;
1126
+ return ca_const_string_scan_run(self, &s,
1127
+ ca_const_string_sort_index_body, NULL);
1128
+ }
1129
+
998
1130
  /* The ordering surface is defined in Ruby (lib/carray/const_string.rb) on
999
1131
  top of these three: a storage cell is a byte range, so storage order is
1000
1132
  insertion order rather than string order, and every member has to read
@@ -1008,18 +1140,18 @@ rb_ca_const_string_sort_index (VALUE self)
1008
1140
  /* CAConstString#min / #max → the byte-min / byte-max element as a frozen String,
1009
1141
  skipping masked elements; nil if empty or all masked. */
1010
1142
  static VALUE
1011
- ca_const_string_extremum (VALUE self, int want_max)
1143
+ ca_const_string_extremum_body (ca_const_string_scan_t *scan, void *arg)
1012
1144
  {
1013
- ca_const_string_scan_t s;
1145
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
1146
+ int want_max = *(int *) arg;
1014
1147
  const char *p, *bestp = NULL;
1015
1148
  int64_t len, bestlen = 0;
1016
1149
  ca_size_t i;
1017
1150
  int found = 0;
1018
1151
 
1019
- ca_const_string_scan_begin(self, &s);
1020
- for ( i = 0; i < s.n; i++ ) {
1152
+ for ( i = 0; i < s->n; i++ ) {
1021
1153
  int c;
1022
- if ( ! ca_const_string_record(&s, i, &p, &len) ) continue;
1154
+ if ( ! ca_const_string_record(s, i, &p, &len) ) continue;
1023
1155
  if ( ! found ) {
1024
1156
  bestp = p; bestlen = len; found = 1;
1025
1157
  continue;
@@ -1031,14 +1163,18 @@ ca_const_string_extremum (VALUE self, int want_max)
1031
1163
  }
1032
1164
  }
1033
1165
  if ( ! found ) {
1034
- ca_const_string_scan_end(&s);
1035
1166
  return Qnil;
1036
1167
  }
1037
- {
1038
- VALUE str = rb_enc_str_new(bestp, bestlen, rb_enc_from_index(s.ca->encoding_id));
1039
- ca_const_string_scan_end(&s);
1040
- return rb_str_freeze(str);
1041
- }
1168
+ return rb_str_freeze(rb_enc_str_new(bestp, bestlen,
1169
+ rb_enc_from_index(s->ca->encoding_id)));
1170
+ }
1171
+
1172
+ static VALUE
1173
+ ca_const_string_extremum (VALUE self, int want_max)
1174
+ {
1175
+ ca_const_string_scan_t s;
1176
+ return ca_const_string_scan_run(self, &s,
1177
+ ca_const_string_extremum_body, &want_max);
1042
1178
  }
1043
1179
 
1044
1180
  static VALUE rb_ca_const_string_min (VALUE self) { return ca_const_string_extremum(self, 0); }
@@ -1062,6 +1198,52 @@ ca_const_string_hash_bytes (const char *p, int64_t len)
1062
1198
  return h;
1063
1199
  }
1064
1200
 
1201
+ typedef struct {
1202
+ uint32_t *codes, *slot;
1203
+ int64_t *lev_start, *lev_len;
1204
+ size_t mask_bits;
1205
+ ca_size_t k;
1206
+ } ca_const_string_factorize_t;
1207
+
1208
+ /* Codes the cells by first appearance into an open-addressing table of
1209
+ (start, length) records. Reads bytes only. */
1210
+ static VALUE
1211
+ ca_const_string_factorize_body (ca_const_string_scan_t *scan, void *arg)
1212
+ {
1213
+ ca_const_string_scan_t s0 = *scan, *s = &s0; /* a local copy: the writes below cannot alias it */
1214
+ ca_const_string_factorize_t *f = (ca_const_string_factorize_t *) arg;
1215
+ ca_size_t i, k = f->k;
1216
+ for ( i = 0; i < s->n; i++ ) {
1217
+ const char *p;
1218
+ int64_t len;
1219
+ size_t j;
1220
+ if ( ! ca_const_string_record(s, i, &p, &len) ) {
1221
+ f->codes[i] = 0xFFFFFFFFu;
1222
+ continue;
1223
+ }
1224
+ j = (size_t) ca_const_string_hash_bytes(p, len) & f->mask_bits;
1225
+ for (;;) {
1226
+ uint32_t c = f->slot[j];
1227
+ if ( c == 0 ) {
1228
+ f->lev_start[k] = (int64_t) (p - s->buf);
1229
+ f->lev_len[k] = len;
1230
+ f->slot[j] = (uint32_t) (k + 1);
1231
+ f->codes[i] = (uint32_t) k;
1232
+ k++;
1233
+ break;
1234
+ }
1235
+ if ( f->lev_len[c - 1] == len &&
1236
+ memcmp(s->buf + f->lev_start[c - 1], p, (size_t) len) == 0 ) {
1237
+ f->codes[i] = c - 1;
1238
+ break;
1239
+ }
1240
+ j = (j + 1) & f->mask_bits;
1241
+ }
1242
+ }
1243
+ f->k = k;
1244
+ return Qnil;
1245
+ }
1246
+
1065
1247
  /* CAConstString#__factorize_appearance__ -> [codes, levels], as the CArray
1066
1248
  method answers: codes are the narrowest unsigned type holding the k codes
1067
1249
  and the type-max sentinel, which masked cells store; levels are the k
@@ -1100,34 +1282,17 @@ rb_ca_const_string_factorize_appearance (VALUE self)
1100
1282
  lev_start = ALLOCV_N(int64_t, start_holder, n > 0 ? n : 1);
1101
1283
  lev_len = ALLOCV_N(int64_t, len_holder, n > 0 ? n : 1);
1102
1284
 
1103
- ca_const_string_scan_begin(self, &s);
1104
- for ( i = 0; i < s.n; i++ ) {
1105
- const char *p;
1106
- int64_t len;
1107
- size_t j;
1108
- if ( ! ca_const_string_record(&s, i, &p, &len) ) {
1109
- codes[i] = 0xFFFFFFFFu;
1110
- continue;
1111
- }
1112
- j = (size_t) ca_const_string_hash_bytes(p, len) & mask_bits;
1113
- for (;;) {
1114
- uint32_t c = slot[j];
1115
- if ( c == 0 ) {
1116
- lev_start[k] = (int64_t) (p - s.buf);
1117
- lev_len[k] = len;
1118
- slot[j] = (uint32_t) (k + 1);
1119
- codes[i] = (uint32_t) k;
1120
- k++;
1121
- break;
1122
- }
1123
- if ( lev_len[c - 1] == len && memcmp(s.buf + lev_start[c - 1], p, (size_t) len) == 0 ) {
1124
- codes[i] = c - 1;
1125
- break;
1126
- }
1127
- j = (j + 1) & mask_bits;
1128
- }
1285
+ {
1286
+ ca_const_string_factorize_t f;
1287
+ f.codes = codes;
1288
+ f.slot = slot;
1289
+ f.lev_start = lev_start;
1290
+ f.lev_len = lev_len;
1291
+ f.mask_bits = mask_bits;
1292
+ f.k = 0;
1293
+ ca_const_string_scan_run(self, &s, ca_const_string_factorize_body, &f);
1294
+ k = f.k;
1129
1295
  }
1130
- ca_const_string_scan_end(&s);
1131
1296
  ALLOCV_END(slot_holder);
1132
1297
 
1133
1298
  /* The buffer is the column's frozen String, alive past the window. */
@@ -1229,15 +1394,15 @@ Init_ca_obj_const_string (void)
1229
1394
  rb_define_method(rb_cCAConstString, "byte_length", rb_ca_const_string_byte_length, 0);
1230
1395
  rb_define_method(rb_cCAConstString, "eq", rb_ca_const_string_eq, 1);
1231
1396
  rb_define_method(rb_cCAConstString, "count", rb_ca_const_string_count, -1);
1232
- rb_define_method(rb_cCAConstString, "start_with?", rb_ca_const_string_start_with, 1);
1233
- rb_define_method(rb_cCAConstString, "end_with?", rb_ca_const_string_end_with, 1);
1397
+ rb_define_method(rb_cCAConstString, "start_with?", rb_ca_const_string_start_with, -1);
1398
+ rb_define_method(rb_cCAConstString, "end_with?", rb_ca_const_string_end_with, -1);
1234
1399
  rb_define_method(rb_cCAConstString, "include?", rb_ca_const_string_include, 1);
1235
1400
 
1236
1401
  /* native exact-lookup search: flat index of the first byte-equal element,
1237
1402
  or nil. `search` is what str_matches consumes; find_value_index is the
1238
1403
  *_index-named surface (both exact, mask-skipping). */
1239
- rb_define_method(rb_cCAConstString, "search", rb_ca_const_string_search, 1);
1240
- rb_define_method(rb_cCAConstString, "find_value_index", rb_ca_const_string_search, 1);
1404
+ rb_define_method(rb_cCAConstString, "search", rb_ca_const_string_search, -1);
1405
+ rb_define_method(rb_cCAConstString, "find_value_index", rb_ca_const_string_find_value_index, 1);
1241
1406
 
1242
1407
  /* Native flat sort permutation / min / max (§3.7), byte-memcmp
1243
1408
  comparator. The public ordering surface is built on these in