carray 3.0.1 → 3.0.2

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -0
  3. data/README.md +2 -2
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +33 -4
  6. data/ext/ca_axis_group.c +202 -96
  7. data/ext/ca_categorical_iterator.c +108 -54
  8. data/ext/ca_kernel_iterator.c +317 -51
  9. data/ext/ca_kernel_iterator.h +142 -35
  10. data/ext/ca_obj_array.c +62 -20
  11. data/ext/ca_obj_block.c +4 -4
  12. data/ext/ca_obj_const_string.c +85 -26
  13. data/ext/ca_obj_face.c +24 -0
  14. data/ext/ca_obj_face.h +15 -0
  15. data/ext/ca_obj_fixlen_string.c +18 -5
  16. data/ext/ca_obj_meld.c +123 -25
  17. data/ext/ca_obj_object.c +8 -0
  18. data/ext/ca_obj_select.c +49 -34
  19. data/ext/ca_obj_stack.c +3 -8
  20. data/ext/ca_obj_stride.c +72 -1
  21. data/ext/ca_obj_string.c +8 -4
  22. data/ext/ca_obj_window.c +8 -2
  23. data/ext/ca_op_ipower.c +1 -2
  24. data/ext/ca_rng_normal.h +42 -0
  25. data/ext/ca_rng_xoshiro256pp.h +105 -0
  26. data/ext/ca_sweep_engine.c +307 -143
  27. data/ext/ca_sweep_engine.h +26 -5
  28. data/ext/carray.h +21 -2
  29. data/ext/carray_access.c +32 -20
  30. data/ext/carray_address_basis.c +590 -0
  31. data/ext/carray_broadcast.c +3 -3
  32. data/ext/carray_call_cfunc.c +667 -483
  33. data/ext/carray_cast.c +115 -41
  34. data/ext/carray_copy.c +55 -30
  35. data/ext/carray_core.c +83 -3
  36. data/ext/carray_count.c +9 -10
  37. data/ext/carray_factorize.c +46 -25
  38. data/ext/carray_internal.h +17 -0
  39. data/ext/carray_kernels_reduce_aggregate.c +168 -0
  40. data/ext/carray_kernels_reduce_cumulative.c +270 -1
  41. data/ext/carray_kernels_reduce_extreme.c +554 -8
  42. data/ext/carray_kernels_scan.c +4 -4
  43. data/ext/carray_kernels_search.c +94 -14
  44. data/ext/carray_loop.c +7 -1
  45. data/ext/carray_mask.c +23 -8
  46. data/ext/carray_median_percentile.c +55 -0
  47. data/ext/carray_operator.c +4 -4
  48. data/ext/carray_order.c +1 -1
  49. data/ext/carray_random.c +384 -40
  50. data/ext/carray_slab.c +13 -0
  51. data/ext/carray_sort.c +20 -22
  52. data/ext/mk_call_cfunc.rb +103 -116
  53. data/ext/mkkernel.rb +297 -29
  54. data/ext/ruby_carray.c +10 -1
  55. data/ext/version.h +4 -4
  56. data/lib/carray/autoload_carray.rb +5 -3
  57. data/lib/carray/autoload_method_extension.rb +12 -0
  58. data/lib/carray/axis_group.rb +77 -0
  59. data/lib/carray/basics.rb +4 -0
  60. data/lib/carray/block_iterator.rb +92 -16
  61. data/lib/carray/categorical.rb +150 -33
  62. data/lib/carray/categorical_iterator.rb +207 -80
  63. data/lib/carray/const_string.rb +131 -27
  64. data/lib/carray/construct.rb +40 -0
  65. data/lib/carray/data_type_extension.rb +3 -0
  66. data/lib/carray/data_type_limits.rb +91 -0
  67. data/lib/carray/fixlen_string.rb +1 -1
  68. data/lib/carray/frame/csv_parser.rb +11 -4
  69. data/lib/carray/frame/frame.rb +81 -10
  70. data/lib/carray/frame/group.rb +36 -3
  71. data/lib/carray/frame/io.rb +67 -15
  72. data/lib/carray/frame/records.rb +18 -4
  73. data/lib/carray/frame/verbs.rb +14 -11
  74. data/lib/carray/inspect.rb +42 -9
  75. data/lib/carray/iterator.rb +143 -0
  76. data/lib/carray/lazy.rb +0 -37
  77. data/lib/carray/mask_gap_fill.rb +3 -1
  78. data/lib/carray/methods/discovery_along.rb +74 -0
  79. data/lib/carray/methods/factorize.rb +50 -0
  80. data/lib/carray/methods/is_in.rb +13 -2
  81. data/lib/carray/methods/locate_addr.rb +75 -2
  82. data/lib/carray/methods/mask_duplicates.rb +35 -1
  83. data/lib/carray/methods/nunique.rb +22 -1
  84. data/lib/carray/methods/repeat.rb +110 -0
  85. data/lib/carray/methods/unique.rb +41 -1
  86. data/lib/carray/rng.rb +86 -0
  87. data/lib/carray/slab_iterator.rb +58 -13
  88. data/lib/carray/string_operation_extension.rb +5 -1
  89. data/lib/carray/time.rb +18 -2
  90. data/lib/carray/window_iterator.rb +142 -20
  91. data/lib/carray.rb +2 -0
  92. data/yard-stubs/ca_obj_block.rb +2 -7
  93. data/yard-stubs/ca_obj_window.rb +10 -2
  94. data/yard-stubs/carray_access.rb +1 -1
  95. data/yard-stubs/carray_broadcast.rb +1 -1
  96. data/yard-stubs/carray_core.rb +0 -80
  97. data/yard-stubs/carray_count.rb +7 -2
  98. data/yard-stubs/carray_lazy.rb +205 -0
  99. data/yard-stubs/carray_math.rb +1486 -3
  100. data/yard-stubs/carray_median_percentile.rb +16 -2
  101. data/yard-stubs/carray_order.rb +9 -69
  102. data/yard-stubs/carray_slab.rb +9 -7
  103. data/yard-stubs/carray_sort.rb +7 -5
  104. metadata +9 -1
data/ext/carray_cast.c CHANGED
@@ -552,6 +552,77 @@ rb_ca_object_to_data_class (VALUE self, VALUE rtype, ca_size_t bytes)
552
552
  return obj;
553
553
  }
554
554
 
555
+ typedef struct {
556
+ CArray *ca;
557
+ CArray *cb;
558
+ boolean8_t *scratch;
559
+ } ca_to_type_ctx_t;
560
+
561
+ /* The cast proper, with `ca` attached. It can raise (a value the target
562
+ type cannot hold), so it runs under rb_ensure. */
563
+ static VALUE
564
+ ca_to_type_cast (VALUE arg)
565
+ {
566
+ ca_to_type_ctx_t *c = (ca_to_type_ctx_t *) arg;
567
+ CArray *ca = c->ca, *cb = c->cb;
568
+
569
+ if ( ca->data_type == CA_OBJECT
570
+ && ( (cb->data_type >= CA_INT8 && cb->data_type <= CA_UINT64)
571
+ || cb->data_type == CA_FLOAT32 || cb->data_type == CA_FLOAT64 ) ) {
572
+ /* object -> int/float: give the cast a mask buffer so an unparseable
573
+ cell becomes UNDEF (see ext/carray_cast_func.rb). Cast into a
574
+ scratch mask seeded from the source mask, then attach it to the
575
+ output only if some cell ended up masked -- an all-valid cast keeps
576
+ the mask-less result the legacy path produced. */
577
+ ca_size_t ne = cb->elements;
578
+ ca_size_t i;
579
+ boolean8_t any = 0;
580
+ boolean8_t *scratch;
581
+ c->scratch = scratch = ALLOC_N(boolean8_t, ne > 0 ? ne : 1);
582
+ if ( cb->mask ) {
583
+ memcpy(scratch, cb->mask->ptr, ne); /* copy of source mask */
584
+ }
585
+ else {
586
+ memset(scratch, 0, ne);
587
+ }
588
+ ca_cast_block_with_mask(ne, ca, ca->ptr, cb, cb->ptr, scratch);
589
+ for (i=0; i<ne; i++) {
590
+ if ( scratch[i] ) {
591
+ any = 1;
592
+ break;
593
+ }
594
+ }
595
+ if ( any ) {
596
+ if ( ! cb->mask ) {
597
+ ca_create_mask(cb);
598
+ }
599
+ memcpy(cb->mask->ptr, scratch, ne);
600
+ }
601
+ xfree(scratch);
602
+ c->scratch = NULL;
603
+ }
604
+ else if ( ca_has_mask(ca) ) {
605
+ ca_cast_block_with_mask(cb->elements, ca, ca->ptr, cb, cb->ptr,
606
+ (boolean8_t*)ca->mask->ptr);
607
+ }
608
+ else {
609
+ ca_cast_block(cb->elements, ca, ca->ptr, cb, cb->ptr);
610
+ }
611
+ return Qnil;
612
+ }
613
+
614
+ static VALUE
615
+ ca_to_type_release (VALUE arg)
616
+ {
617
+ ca_to_type_ctx_t *c = (ca_to_type_ctx_t *) arg;
618
+ if ( c->scratch ) {
619
+ xfree(c->scratch);
620
+ c->scratch = NULL;
621
+ }
622
+ ca_detach(c->ca);
623
+ return Qnil;
624
+ }
625
+
555
626
  /* CArray#to_type(data_type, bytes:) -- eager copy of self converted to
556
627
  data_type (a new entity owning its storage). User doc lives in
557
628
  yard-stubs/carray_cast.rb. */
@@ -561,6 +632,7 @@ rb_ca_to_type_internal (int argc, VALUE *argv, VALUE self)
561
632
  {
562
633
  volatile VALUE obj, rtype = Qnil, ropt, rbytes = Qnil;
563
634
  CArray *ca, *cb;
635
+ ca_to_type_ctx_t ctx;
564
636
  int8_t data_type;
565
637
  ca_size_t bytes;
566
638
 
@@ -601,48 +673,11 @@ rb_ca_to_type_internal (int argc, VALUE *argv, VALUE self)
601
673
 
602
674
  TypedData_Get_Struct(obj, CArray, &carray_data_type, cb);
603
675
 
676
+ ctx.ca = ca;
677
+ ctx.cb = cb;
678
+ ctx.scratch = NULL;
604
679
  ca_attach(ca);
605
- if ( ca->data_type == CA_OBJECT
606
- && ( (data_type >= CA_INT8 && data_type <= CA_UINT64)
607
- || data_type == CA_FLOAT32 || data_type == CA_FLOAT64 ) ) {
608
- /* object -> int/float: give the cast a mask buffer so an unparseable
609
- cell becomes UNDEF (see ext/carray_cast_func.rb). Cast into a
610
- scratch mask seeded from the source mask, then attach it to the
611
- output only if some cell ended up masked -- an all-valid cast keeps
612
- the mask-less result the legacy path produced. */
613
- ca_size_t ne = cb->elements;
614
- ca_size_t i;
615
- boolean8_t any = 0;
616
- boolean8_t *scratch = ALLOC_N(boolean8_t, ne > 0 ? ne : 1);
617
- if ( cb->mask ) {
618
- memcpy(scratch, cb->mask->ptr, ne); /* copy of source mask */
619
- }
620
- else {
621
- memset(scratch, 0, ne);
622
- }
623
- ca_cast_block_with_mask(ne, ca, ca->ptr, cb, cb->ptr, scratch);
624
- for (i=0; i<ne; i++) {
625
- if ( scratch[i] ) {
626
- any = 1;
627
- break;
628
- }
629
- }
630
- if ( any ) {
631
- if ( ! cb->mask ) {
632
- ca_create_mask(cb);
633
- }
634
- memcpy(cb->mask->ptr, scratch, ne);
635
- }
636
- xfree(scratch);
637
- }
638
- else if ( ca_has_mask(ca) ) {
639
- ca_cast_block_with_mask(cb->elements, ca, ca->ptr, cb, cb->ptr,
640
- (boolean8_t*)ca->mask->ptr);
641
- }
642
- else {
643
- ca_cast_block(cb->elements, ca, ca->ptr, cb, cb->ptr);
644
- }
645
- ca_detach(ca);
680
+ rb_ensure(ca_to_type_cast, (VALUE) &ctx, ca_to_type_release, (VALUE) &ctx);
646
681
 
647
682
  /* When rtype is a data_class (e.g. CAStruct subclass), wrap the
648
683
  result in CARecord so the cast output carries data_class. */
@@ -1366,6 +1401,36 @@ rb_ca_cast (volatile VALUE self)
1366
1401
  return obj;
1367
1402
  }
1368
1403
 
1404
+ /* A String operand against a fixlen array is a value of that array's cell
1405
+ width. ca_value_to_data_type answers CA_OBJECT for a String, so without
1406
+ this the operand became an object scalar and the comparison ran down the
1407
+ object lane -- an rb_funcall of String#== per cell, against the cell's
1408
+ NUL-padded text. A fixlen array pads a short String on write
1409
+ (`a[i] = "be"` stores "be\0\0\0"), so that answered that an array does
1410
+ not equal the string it was written from, and did it 30x slower than the
1411
+ memcmp lane the fixlen comparison bodies are there to provide.
1412
+
1413
+ Two fixlen *arrays* of different widths keep the variable-width byte
1414
+ string semantics those bodies implement (min-width memcmp with a length
1415
+ tiebreak); this is only about a scalar standing in for a cell. */
1416
+ static int
1417
+ ca_fixlen_scalar_operand (volatile VALUE *scalar, VALUE reference)
1418
+ {
1419
+ CArray *cr;
1420
+ /* A String is the only operand that means a cell value here. A Regexp
1421
+ (match) or any other object keeps its own coercion: those are asking
1422
+ something about the bytes, not standing in for them. */
1423
+ if ( ! RB_TYPE_P(*scalar, T_STRING) || ! rb_obj_is_carray(reference) ) {
1424
+ return 0;
1425
+ }
1426
+ TypedData_Get_Struct(reference, CArray, &carray_data_type, cr);
1427
+ if ( cr->data_type != CA_FIXLEN ) {
1428
+ return 0;
1429
+ }
1430
+ *scalar = rb_cscalar_new_with_value(CA_FIXLEN, cr->bytes, *scalar);
1431
+ return 1;
1432
+ }
1433
+
1369
1434
  /* CArray.cast(value) -- singleton entry delegating to rb_ca_cast, which
1370
1435
  returns a CArray unchanged and coerces a Ruby value to a CScalar / CArray.
1371
1436
  User doc lives in yard-stubs/carray_cast.rb. */
@@ -1409,6 +1474,9 @@ rb_ca_cast_self_or_other (volatile VALUE *self, volatile VALUE *other)
1409
1474
  *self = rb_cscalar_new_with_value(CA_CMPLX128, 0, *self);
1410
1475
  }
1411
1476
  #endif
1477
+ else if ( ca_fixlen_scalar_operand(self, *other) ) {
1478
+ /* width taken from the fixlen reference; see the helper above */
1479
+ }
1412
1480
  else if ( rb_ca_is_float_type(*other) ) {
1413
1481
  *self = rb_cscalar_new_with_value(CA_FLOAT64, 0, *self);
1414
1482
  }
@@ -1437,6 +1505,9 @@ rb_ca_cast_self_or_other (volatile VALUE *self, volatile VALUE *other)
1437
1505
  *other = rb_cscalar_new_with_value(CA_CMPLX128, 0, *other);
1438
1506
  }
1439
1507
  #endif
1508
+ else if ( ca_fixlen_scalar_operand(other, *self) ) {
1509
+ /* width taken from the fixlen reference; see the helper above */
1510
+ }
1440
1511
  else if ( rb_ca_is_float_type(*self) ) {
1441
1512
  *other = rb_cscalar_new_with_value(CA_FLOAT64, 0, *other);
1442
1513
  }
@@ -1824,6 +1895,9 @@ rb_ca_cast_other (VALUE *self, volatile VALUE *other)
1824
1895
  if ( rb_ca_is_object_type(*self) ) {
1825
1896
  *other = rb_cscalar_new_with_value(CA_OBJECT, 0, *other);
1826
1897
  }
1898
+ else if ( ca_fixlen_scalar_operand(other, *self) ) {
1899
+ return; /* already the receiver's data_type and width */
1900
+ }
1827
1901
  else if ( rb_ca_is_float_type(*self) ) {
1828
1902
  *other = rb_cscalar_new_with_value(CA_FLOAT64, 0, *other);
1829
1903
  }
data/ext/carray_copy.c CHANGED
@@ -11,6 +11,7 @@
11
11
  #include "carray.h"
12
12
  #include "ca_obj_face.h" /* CA_FACE_LIFT_IF_FACE */
13
13
  #include "carray_internal.h" /* ca_gc_hold_push / ca_gc_hold_pop_to */
14
+ #include "ca_sweep_engine.h" /* ca_sweep_same_shape / ca_sweep_refuse_shapes */
14
15
  #include <stdarg.h>
15
16
 
16
17
  /* ------------------------------------------------------------------- */
@@ -19,22 +20,27 @@
19
20
  * and bytes as `ca`, copies the element payload, and reproduces the
20
21
  * mask state. Always copies even when `ca` is an entity; for view
21
22
  * sources the data path goes through `ca_copy_data`, otherwise a flat
22
- * memcpy of `ca_length(ca)` bytes.
23
+ * memcpy of `ca_length(ca)` bytes. If reading the source raises, the
24
+ * copy is freed before the raise propagates.
23
25
  *
24
26
  * Backs `CArray#copy` and is also reachable as a C-level utility from
25
27
  * other ext files that need an owned snapshot. */
26
- CArray *
27
- ca_copy (void *ap)
28
+ void
29
+ ca_fill_or_free (CArray *co, VALUE (*fill)(VALUE), VALUE arg)
28
30
  {
29
- CArray *ca = (CArray *) ap;
30
- CArray *co;
31
-
32
- if ( ca_is_scalar(ca) ) {
33
- co = (CArray *) cscalar_new(ca->data_type, ca->bytes, 0);
34
- }
35
- else {
36
- co = carray_new(ca->data_type, ca->ndim, ca->dim, ca->bytes, 0);
31
+ int tag = 0;
32
+ rb_protect(fill, arg, &tag);
33
+ if ( tag ) {
34
+ ca_free(co);
35
+ rb_jump_tag(tag);
37
36
  }
37
+ }
38
+
39
+ static VALUE
40
+ ca_copy_fill (VALUE arg)
41
+ {
42
+ CArray **pair = (CArray **) arg;
43
+ CArray *ca = pair[0], *co = pair[1];
38
44
 
39
45
  if ( ca_is_attached(ca) ) {
40
46
  memcpy(co->ptr, ca->ptr, ca_length(ca));
@@ -47,6 +53,26 @@ ca_copy (void *ap)
47
53
  if ( ca->mask ) {
48
54
  ca_copy_mask(co, ca);
49
55
  }
56
+ return Qnil;
57
+ }
58
+
59
+ CArray *
60
+ ca_copy (void *ap)
61
+ {
62
+ CArray *ca = (CArray *) ap;
63
+ CArray *co;
64
+ CArray *pair[2];
65
+
66
+ if ( ca_is_scalar(ca) ) {
67
+ co = (CArray *) cscalar_new(ca->data_type, ca->bytes, 0);
68
+ }
69
+ else {
70
+ co = carray_new(ca->data_type, ca->ndim, ca->dim, ca->bytes, 0);
71
+ }
72
+
73
+ pair[0] = ca;
74
+ pair[1] = co;
75
+ ca_fill_or_free(co, ca_copy_fill, (VALUE) pair);
50
76
 
51
77
  return co;
52
78
  }
@@ -272,16 +298,16 @@ rb_ca_template_with_type (VALUE self, VALUE rtype, VALUE rbytes)
272
298
  return rb_ca_template_method(2, args, self);
273
299
  }
274
300
 
275
- /* Picks the largest-shape CArray among `n` variadic CArray arguments
276
- * and returns `template(largest)`. Used by `carray_call_cfunc.c` to
277
- * size the output of vectorised scalar C-function calls, where the
278
- * inputs may be a mix of scalars and arrays. */
301
+ /* Returns `template` of the first non-scalar among `n` variadic CArray
302
+ * arguments (of the first argument when all are scalars). Used by
303
+ * `carray_call_cfunc.c` to size the output of vectorised scalar
304
+ * C-function calls: a scalar pairs with any array, two arrays only when
305
+ * their shapes agree. */
279
306
  VALUE
280
307
  rb_ca_template_n (int n, ...)
281
308
  {
282
- volatile VALUE varg, obj;
283
- CArray *ca;
284
- ca_size_t elements = -1;
309
+ volatile VALUE varg, obj = Qnil;
310
+ CArray *ca, *donor = NULL;
285
311
  va_list vargs;
286
312
  int i;
287
313
 
@@ -289,24 +315,23 @@ rb_ca_template_n (int n, ...)
289
315
  for (i=0; i<n; i++) {
290
316
  varg = va_arg(vargs, VALUE);
291
317
  if ( ! rb_obj_is_carray(varg) ) {
318
+ va_end(vargs);
292
319
  rb_raise(rb_eRuntimeError, "[BUG] not-carray object given to rb_ca_template_n");
293
320
  }
294
321
  TypedData_Get_Struct(varg, CArray, &carray_data_type, ca);
295
322
  if ( i == 0 ) {
296
323
  obj = varg;
297
- elements = ca->elements;
298
324
  }
299
- else {
300
- if ( rb_obj_is_cscalar(varg) ) {
301
- continue;
302
- }
303
- if ( rb_obj_is_cscalar(obj) ) {
304
- obj = varg;
305
- elements = ca->elements;
306
- }
307
- else if ( ca->elements != elements ) {
308
- rb_raise(rb_eRuntimeError, "size mismatch");
309
- }
325
+ if ( ca_is_scalar(ca) ) {
326
+ continue;
327
+ }
328
+ if ( ! donor ) {
329
+ donor = ca;
330
+ obj = varg;
331
+ }
332
+ else if ( ! ca_sweep_same_shape(donor, ca) ) {
333
+ va_end(vargs);
334
+ ca_sweep_refuse_shapes(donor, ca);
310
335
  }
311
336
  }
312
337
  va_end(vargs);
data/ext/carray_core.c CHANGED
@@ -1655,6 +1655,18 @@ ca_allocate (void *ap)
1655
1655
  ca_allocate(ca->mask);
1656
1656
  }
1657
1657
 
1658
+ /* The first attach of a view: materialise through the type's slot, then
1659
+ its mask. Run under rb_protect by ca_attach. */
1660
+ static VALUE
1661
+ ca_attach_view_first (VALUE arg)
1662
+ {
1663
+ CArray *ca = (CArray *) arg;
1664
+ ca_func[ca->obj_type].attach(ca);
1665
+ ca_update_mask(ca);
1666
+ ca_attach(ca->mask);
1667
+ return Qnil;
1668
+ }
1669
+
1658
1670
  /* attach parent's data to ca->ptr */
1659
1671
 
1660
1672
  void
@@ -1668,14 +1680,30 @@ ca_attach (void *ap)
1668
1680
 
1669
1681
  if ( ca_is_view(ca) ) { /* view array */
1670
1682
 
1671
- CAVIEW(ca)->attach += 1; /* increments attach level */
1672
- if ( CAVIEW(ca)->attach > CA_ATTACH_MAX ) {
1683
+ if ( CAVIEW(ca)->attach >= CA_ATTACH_MAX ) {
1673
1684
  rb_raise(rb_eRuntimeError,
1674
1685
  "too large attach count of view array");
1675
1686
  }
1687
+ CAVIEW(ca)->attach += 1; /* increments attach level */
1676
1688
 
1677
1689
  if ( ! ca->ptr ) {
1678
- ca_func[ca->obj_type].attach(ap);
1690
+ /* A slot can raise part way (a conversion meeting a value it cannot
1691
+ hold). Undo the attach then, or the next attach finds ca->ptr
1692
+ set, skips the slot and reads a half-filled buffer. A slot that
1693
+ got as far as publishing ca->ptr has also attached what its
1694
+ detach slot releases; one that raised earlier has published
1695
+ nothing and released its own parent attach on the way out. */
1696
+ int tag = 0;
1697
+ rb_protect(ca_attach_view_first, (VALUE) ca, &tag);
1698
+ if ( tag ) {
1699
+ if ( ca->ptr ) {
1700
+ ca_func[ca->obj_type].detach(ca);
1701
+ ca->ptr = NULL;
1702
+ }
1703
+ CAVIEW(ca)->attach -= 1;
1704
+ rb_jump_tag(tag);
1705
+ }
1706
+ return;
1679
1707
  }
1680
1708
  }
1681
1709
  else { /* entity array */
@@ -1686,6 +1714,43 @@ ca_attach (void *ap)
1686
1714
  ca_attach(ca->mask);
1687
1715
  }
1688
1716
 
1717
+ /* Attach every array in list[0..n-1], or, if one of those attaches
1718
+ raises, none of them: the ones already attached are detached before the
1719
+ raise propagates. For views over several parents. */
1720
+
1721
+ typedef struct {
1722
+ CArray **list;
1723
+ int32_t n;
1724
+ int32_t done;
1725
+ } ca_attach_all_ctx_t;
1726
+
1727
+ static VALUE
1728
+ ca_attach_all_body (VALUE arg)
1729
+ {
1730
+ ca_attach_all_ctx_t *c = (ca_attach_all_ctx_t *) arg;
1731
+ for ( ; c->done < c->n; c->done++ ) {
1732
+ ca_attach(c->list[c->done]);
1733
+ }
1734
+ return Qnil;
1735
+ }
1736
+
1737
+ void
1738
+ ca_attach_all (CArray **list, int32_t n)
1739
+ {
1740
+ ca_attach_all_ctx_t c;
1741
+ int tag = 0;
1742
+ c.list = list;
1743
+ c.n = n;
1744
+ c.done = 0;
1745
+ rb_protect(ca_attach_all_body, (VALUE) &c, &tag);
1746
+ if ( tag ) {
1747
+ while ( c.done > 0 ) {
1748
+ ca_detach(list[--c.done]);
1749
+ }
1750
+ rb_jump_tag(tag);
1751
+ }
1752
+ }
1753
+
1689
1754
  /* attach parent's data to ca->ptr */
1690
1755
 
1691
1756
  void
@@ -2087,6 +2152,17 @@ ca_fill (void *ap, void *aptr)
2087
2152
 
2088
2153
  /* ------------------------------------------------------------------- */
2089
2154
 
2155
+ #ifdef CARRAY_DEV_BUILD
2156
+
2157
+ /* The Ruby attach surface is built into development builds only. Ruby has no
2158
+ raw pointer, so the only thing a block can do inside an attach window is call
2159
+ back into the array API -- and which door that takes is invisible in the
2160
+ syntax: v[0] reads the attached buffer while v[] and v[0..1] compose past it
2161
+ to the root (R5, see the contract beside ca_attach in carray.h). Per-cell and
2162
+ whole-array writes reach the buffer and survive the sync; a write through a
2163
+ derived view is discarded. The in-tree tests drive the healthy spellings, so
2164
+ the surface stays reachable here and out of released builds. */
2165
+
2090
2166
  /* subroutines for CArray.attach, CArray.attach!
2091
2167
  CArray#attach, CArray#attach! */
2092
2168
 
@@ -2307,6 +2383,8 @@ rb_ca__detach__ (VALUE self)
2307
2383
  return self;
2308
2384
  }
2309
2385
 
2386
+ #endif /* CARRAY_DEV_BUILD */
2387
+
2310
2388
  /* ------------------------------------------------------------------- */
2311
2389
 
2312
2390
  static ID id_decode, id_encode;
@@ -2515,6 +2593,7 @@ Init_carray_core (void)
2515
2593
 
2516
2594
  ca_init_obj_type();
2517
2595
 
2596
+ #ifdef CARRAY_DEV_BUILD
2518
2597
  rb_define_singleton_method(rb_cCArray, "attach", rb_ca_s_attach, -1);
2519
2598
  rb_define_singleton_method(rb_cCArray, "attach!", rb_ca_s_attach_bang, -1);
2520
2599
 
@@ -2524,6 +2603,7 @@ Init_carray_core (void)
2524
2603
  rb_define_method(rb_cCArray, "__attach__", rb_ca__attach__, 0);
2525
2604
  rb_define_method(rb_cCArray, "__sync__", rb_ca__sync__, 0);
2526
2605
  rb_define_method(rb_cCArray, "__detach__", rb_ca__detach__, 0);
2606
+ #endif
2527
2607
 
2528
2608
  rb_define_method(rb_cCArray, "members", rb_ca_members, 0);
2529
2609
 
data/ext/carray_count.c CHANGED
@@ -39,11 +39,12 @@ VALUE rb_ca_count_not_masked (int argc, VALUE *argv, VALUE self);
39
39
  - v is a CArray -> broadcast: count each v[k] and stack
40
40
  (rejected for boolean self; see below)
41
41
  - self is boolean, v scalar -> v must be true/false literal
42
- - self is numeric, v scalar -> count_equal_ki (numeric equality)
42
+ - self is numeric or object, v scalar -> count_equal_ki (numeric ==
43
+ for a number, Ruby == for an object)
43
44
 
44
- FIXLEN and OBJECT data_types raise CArray::DataTypeError (the
45
- kernel-iterator helpers do not implement them). Extend
46
- count_equal_ki if demand returns. */
45
+ - self is fixlen, v scalar -> count_equal_ki (memcmp over the whole
46
+ cell; a short String query is padded
47
+ out to the cell width) */
47
48
 
48
49
  static VALUE
49
50
  rb_ca_count (int argc, VALUE *argv, VALUE self)
@@ -95,11 +96,7 @@ rb_ca_count (int argc, VALUE *argv, VALUE self)
95
96
  }
96
97
  if ( ca_test_flag(src, CA_FLAG_FACE_COMPARABLE_STORAGE) ) {
97
98
  if ( ! query_was_scalar ) {
98
- CArray *cv;
99
- GetCArray(rval, cv);
100
- if ( ca_is_face(cv) ) {
101
- rval = rb_ca_strip_face_value(rval);
102
- }
99
+ rval = ca_face_operand_descend(rval, "count(v)");
103
100
  }
104
101
  }
105
102
  else if ( rb_respond_to(self_ref, rb_intern("to_comparable")) ) {
@@ -290,7 +287,9 @@ rb_ca_count (int argc, VALUE *argv, VALUE self)
290
287
  rb_obj_classname(rval));
291
288
  }
292
289
  } else {
293
- if ( rval == Qtrue || rval == Qfalse ) {
290
+ /* true / false are the boolean array's own domain, not a number --
291
+ except in an object array, where they are ordinary stored values. */
292
+ if ( ( rval == Qtrue || rval == Qfalse ) && src->data_type != CA_OBJECT ) {
294
293
  rb_raise(rb_eTypeError,
295
294
  "count(v) on numeric array: v must be numeric, got %s",
296
295
  rb_obj_classname(rval));
@@ -105,13 +105,27 @@
105
105
  /* Descend `self` when it is an ORDERABLE Face; *pface keeps the pre-strip Face
106
106
  for the re-lift (Qnil when there is nothing to put back). */
107
107
  static VALUE
108
- fz_face_descend (VALUE self, volatile VALUE *pface)
108
+ fz_face_descend (VALUE self, volatile VALUE *pface, const char *name)
109
109
  {
110
110
  CArray *ca;
111
111
  GetCArray(self, ca);
112
- if ( ca_is_face(ca) && ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
113
- *pface = self;
114
- return rb_ca_strip_face_value(self);
112
+ if ( ca_is_face(ca) ) {
113
+ if ( ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
114
+ *pface = self;
115
+ return rb_ca_strip_face_value(self);
116
+ }
117
+ /* A Face that cannot be descended must not flow on undescended: whatever
118
+ reads it next reads its storage anyway, which is the encoding rather
119
+ than the values. A Face in this position normally never arrives --
120
+ CAConstString and CACategorical define their own members in Ruby -- but
121
+ the overrides only fire when the Face is the receiver, and this same
122
+ helper descends the *reference* for locate_addr, where it is the
123
+ argument. */
124
+ rb_raise(rb_eArgError,
125
+ "%s: %s is not orderable by storage (a cell encodes the value "
126
+ "rather than being it); convert it first -- #to_string for a "
127
+ "string Face -- or pass .parent to work in storage space",
128
+ name, rb_obj_classname(self));
115
129
  }
116
130
  *pface = Qnil;
117
131
  return self;
@@ -140,15 +154,7 @@ fz_face_reconcile (VALUE reference, VALUE operand, const char *name)
140
154
  rb_intern("to_comparable"),
141
155
  1, operand));
142
156
  }
143
- if ( rb_obj_is_carray(operand) ) {
144
- CArray *op;
145
- GetCArray(operand, op);
146
- if ( ca_is_face(op) ) {
147
- return rb_ca_strip_face_value(operand);
148
- }
149
- }
150
- (void) name;
151
- return operand;
157
+ return ca_face_operand_descend(operand, name);
152
158
  }
153
159
 
154
160
  /* Put the Face back on a value-carrying output. */
@@ -495,7 +501,7 @@ rb_ca_factorize_appearance (VALUE self)
495
501
  {
496
502
  CArray *ca;
497
503
  volatile VALUE face;
498
- self = fz_face_descend(self, &face);
504
+ self = fz_face_descend(self, &face, "categorize");
499
505
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
500
506
 
501
507
  int8_t dt = ca->data_type;
@@ -863,7 +869,7 @@ rb_ca_unique_flat (VALUE self)
863
869
  {
864
870
  CArray *ca;
865
871
  volatile VALUE face;
866
- self = fz_face_descend(self, &face);
872
+ self = fz_face_descend(self, &face, "unique");
867
873
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
868
874
 
869
875
  int8_t dt = ca->data_type;
@@ -1113,7 +1119,7 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1113
1119
  {
1114
1120
  CArray *ca, *cv;
1115
1121
  volatile VALUE face;
1116
- self = fz_face_descend(self, &face);
1122
+ self = fz_face_descend(self, &face, "is_in");
1117
1123
  rvalues = fz_face_reconcile(face, rvalues, "is_in");
1118
1124
  GetCArray(self, ca);
1119
1125
 
@@ -1137,9 +1143,16 @@ rb_ca_is_in (VALUE self, VALUE rvalues)
1137
1143
  rb_raise(rb_eArgError, "__is_in__: values must be a CArray");
1138
1144
  }
1139
1145
  GetCArray(rvalues, cv);
1140
- if ( cv->data_type != dt || (dt == CA_FIXLEN && cv->bytes != ca->bytes) ) {
1146
+ if ( cv->data_type != dt ) {
1147
+ rb_raise(rb_eCADataTypeError,
1148
+ "__is_in__: values data type (%s) must match self (%s)",
1149
+ ca_type_name[cv->data_type], ca_type_name[dt]);
1150
+ }
1151
+ if ( dt == CA_FIXLEN && cv->bytes != ca->bytes ) {
1141
1152
  rb_raise(rb_eCADataTypeError,
1142
- "__is_in__: values data type must match self (%d)", dt);
1153
+ "__is_in__: a fixlen value is a cell-width blob, so the values "
1154
+ "must be %ld bytes wide like self, not %ld",
1155
+ (long) ca->bytes, (long) cv->bytes);
1143
1156
  }
1144
1157
  if ( cv->ndim < 1 ) {
1145
1158
  rb_raise(rb_eRuntimeError, "__is_in__: values need ndim >= 1");
@@ -1271,7 +1284,7 @@ rb_ca_locate_addr (VALUE self, VALUE rref)
1271
1284
  output is an address, so nothing is lifted back. */
1272
1285
  {
1273
1286
  volatile VALUE ref_face;
1274
- rref = fz_face_descend(rref, &ref_face);
1287
+ rref = fz_face_descend(rref, &ref_face, "locate_addr");
1275
1288
  self = fz_face_reconcile(ref_face, self, "locate_addr");
1276
1289
  }
1277
1290
  GetCArray(self, ca);
@@ -1531,7 +1544,7 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1531
1544
  {
1532
1545
  CArray *ca, *co;
1533
1546
  volatile VALUE face;
1534
- self = fz_face_descend(self, &face);
1547
+ self = fz_face_descend(self, &face, "set relation");
1535
1548
  rother = fz_face_reconcile(face, rother, "set relation");
1536
1549
  GetCArray(self, ca);
1537
1550
 
@@ -1554,8 +1567,16 @@ fz_set_relation (VALUE self, VALUE rother, int keep_when_hit)
1554
1567
  rb_raise(rb_eArgError, "set relation: other must be a CArray");
1555
1568
  }
1556
1569
  GetCArray(rother, co);
1557
- if ( co->data_type != dt || (dt == CA_FIXLEN && co->bytes != ca->bytes) ) {
1558
- rb_raise(rb_eCADataTypeError, "set relation: other data type must match self (%d)", dt);
1570
+ if ( co->data_type != dt ) {
1571
+ rb_raise(rb_eCADataTypeError,
1572
+ "set relation: other data type (%s) must match self (%s)",
1573
+ ca_type_name[co->data_type], ca_type_name[dt]);
1574
+ }
1575
+ if ( dt == CA_FIXLEN && co->bytes != ca->bytes ) {
1576
+ rb_raise(rb_eCADataTypeError,
1577
+ "set relation: a fixlen value is a cell-width blob, so other "
1578
+ "must be %ld bytes wide like self, not %ld",
1579
+ (long) ca->bytes, (long) co->bytes);
1559
1580
  }
1560
1581
  if ( co->ndim < 1 ) {
1561
1582
  rb_raise(rb_eRuntimeError, "set relation: other need ndim >= 1");
@@ -1716,7 +1737,7 @@ rb_ca_set_union (VALUE self, VALUE rother)
1716
1737
  {
1717
1738
  CArray *ca, *co;
1718
1739
  volatile VALUE face;
1719
- self = fz_face_descend(self, &face);
1740
+ self = fz_face_descend(self, &face, "union");
1720
1741
  rother = fz_face_reconcile(face, rother, "union");
1721
1742
  GetCArray(self, ca);
1722
1743
 
@@ -1793,7 +1814,7 @@ rb_ca_value_counts_flat (VALUE self)
1793
1814
  {
1794
1815
  CArray *ca;
1795
1816
  volatile VALUE face;
1796
- self = fz_face_descend(self, &face);
1817
+ self = fz_face_descend(self, &face, "value_counts");
1797
1818
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
1798
1819
 
1799
1820
  int8_t dt = ca->data_type;
@@ -2332,7 +2353,7 @@ rb_ca_mode_axis (VALUE self, VALUE vaxis)
2332
2353
  {
2333
2354
  CArray *ca;
2334
2355
  volatile VALUE face;
2335
- self = fz_face_descend(self, &face);
2356
+ self = fz_face_descend(self, &face, "mode");
2336
2357
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
2337
2358
 
2338
2359
  int8_t dt = ca->data_type;