carray 3.0.2 → 3.0.4

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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +568 -0
  3. data/README.md +6 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_descriptor.h +27 -24
  6. data/ext/ca_axis_dispatch.c +220 -0
  7. data/ext/ca_axis_group.c +201 -123
  8. data/ext/ca_binop_dispatch.c +46 -52
  9. data/ext/ca_binop_dispatch.h +1 -1
  10. data/ext/ca_categorical_iterator.c +249 -183
  11. data/ext/ca_compare.c +7 -12
  12. data/ext/ca_for_buffer.h +21 -68
  13. data/ext/ca_group_iter.c +38 -23
  14. data/ext/ca_kernel_iterator.c +1765 -2076
  15. data/ext/ca_kernel_iterator.h +584 -566
  16. data/ext/ca_monop_dispatch.c +86 -63
  17. data/ext/ca_monop_dispatch.h +5 -0
  18. data/ext/ca_obj_array.c +223 -56
  19. data/ext/ca_obj_bincmp.c +172 -138
  20. data/ext/ca_obj_binop.c +116 -230
  21. data/ext/ca_obj_bitarray.c +40 -26
  22. data/ext/ca_obj_bitfield.c +100 -68
  23. data/ext/ca_obj_block.c +19 -9
  24. data/ext/ca_obj_byte_swap.c +45 -46
  25. data/ext/ca_obj_const_string.c +269 -44
  26. data/ext/ca_obj_face.c +612 -89
  27. data/ext/ca_obj_face.h +111 -47
  28. data/ext/ca_obj_fake.c +36 -79
  29. data/ext/ca_obj_farray.c +7 -3
  30. data/ext/ca_obj_field.c +33 -8
  31. data/ext/ca_obj_fixlen_string.c +23 -24
  32. data/ext/ca_obj_grid.c +541 -210
  33. data/ext/ca_obj_meld.c +331 -111
  34. data/ext/ca_obj_moncmp.c +80 -65
  35. data/ext/ca_obj_monop.c +177 -149
  36. data/ext/ca_obj_object.c +664 -248
  37. data/ext/ca_obj_record.c +63 -23
  38. data/ext/ca_obj_reduce.c +18 -16
  39. data/ext/ca_obj_refer.c +140 -58
  40. data/ext/ca_obj_remap.c +85 -24
  41. data/ext/ca_obj_repeat.c +24 -9
  42. data/ext/ca_obj_roll.c +121 -16
  43. data/ext/ca_obj_select.c +61 -58
  44. data/ext/ca_obj_select_axis.c +73 -107
  45. data/ext/ca_obj_shift.c +32 -102
  46. data/ext/ca_obj_stack.c +372 -122
  47. data/ext/ca_obj_stride.c +348 -140
  48. data/ext/ca_obj_string.c +88 -22
  49. data/ext/ca_obj_tile.c +38 -21
  50. data/ext/ca_obj_time.c +45 -31
  51. data/ext/ca_obj_timedelta.c +43 -29
  52. data/ext/ca_obj_transpose.c +17 -6
  53. data/ext/ca_obj_triop.c +125 -141
  54. data/ext/ca_obj_window.c +330 -44
  55. data/ext/ca_op_ipower.c +24 -11
  56. data/ext/ca_op_powi.h +57 -30
  57. data/ext/ca_pow5_table.h +656 -0
  58. data/ext/ca_sort_kernels.h +70 -0
  59. data/ext/ca_sweep_engine.c +104 -81
  60. data/ext/ca_sweep_engine.h +7 -20
  61. data/ext/ca_triop_dispatch.c +4 -1
  62. data/ext/ca_triop_dispatch.h +5 -2
  63. data/ext/caframe_csv_reader.c +542 -0
  64. data/ext/carray.h +241 -92
  65. data/ext/carray_access.c +608 -276
  66. data/ext/carray_address_basis.c +494 -97
  67. data/ext/carray_attribute.c +129 -4
  68. data/ext/carray_bincount.c +39 -7
  69. data/ext/carray_broadcast.c +56 -19
  70. data/ext/carray_build_flags.h +1 -1
  71. data/ext/carray_call_cfunc.c +108 -28
  72. data/ext/carray_cast.c +1576 -223
  73. data/ext/carray_cast_func.rb +77 -24
  74. data/ext/carray_class.c +4 -3
  75. data/ext/carray_conversion.c +208 -75
  76. data/ext/carray_copy.c +82 -24
  77. data/ext/carray_core.c +646 -162
  78. data/ext/carray_count.c +171 -88
  79. data/ext/carray_element.c +94 -60
  80. data/ext/carray_factorize.c +670 -324
  81. data/ext/carray_generate.c +260 -83
  82. data/ext/carray_histogram.c +209 -106
  83. data/ext/carray_hold.c +14 -16
  84. data/ext/carray_index_classifier.c +422 -243
  85. data/ext/carray_internal.h +692 -3
  86. data/ext/carray_kernels_bincmp.c +7 -0
  87. data/ext/carray_kernels_binop.c +487 -120
  88. data/ext/carray_kernels_init.c +653 -50
  89. data/ext/carray_kernels_moncmp.c +15 -8
  90. data/ext/carray_kernels_monop.c +99 -43
  91. data/ext/carray_kernels_reduce_aggregate.c +3925 -6112
  92. data/ext/carray_kernels_reduce_boolean.c +112 -36
  93. data/ext/carray_kernels_reduce_cumulative.c +2127 -6903
  94. data/ext/carray_kernels_reduce_extreme.c +3032 -2892
  95. data/ext/carray_kernels_reduce_variance.c +439 -500
  96. data/ext/carray_kernels_scan.c +1097 -488
  97. data/ext/carray_kernels_search.c +3787 -2176
  98. data/ext/carray_kernels_sort.c +1200 -920
  99. data/ext/carray_kernels_triop.c +479 -7
  100. data/ext/carray_key_windows.c +216 -0
  101. data/ext/carray_lazy.c +967 -153
  102. data/ext/carray_loop.c +81 -65
  103. data/ext/carray_mask.c +910 -324
  104. data/ext/carray_math_kernel.h +1 -0
  105. data/ext/carray_median_percentile.c +299 -100
  106. data/ext/carray_memory_view.c +872 -758
  107. data/ext/carray_operator.c +1171 -364
  108. data/ext/carray_order.c +505 -133
  109. data/ext/carray_partition.c +112 -65
  110. data/ext/carray_random.c +327 -239
  111. data/ext/carray_round.c +273 -0
  112. data/ext/carray_scatter.c +346 -78
  113. data/ext/carray_segment.c +257 -0
  114. data/ext/carray_slab.c +249 -86
  115. data/ext/carray_sort.c +224 -227
  116. data/ext/carray_sort_kernel.c +8 -1
  117. data/ext/carray_struct.c +118 -39
  118. data/ext/carray_test.c +193 -69
  119. data/ext/carray_time_text.c +1182 -0
  120. data/ext/carray_undef.c +4 -0
  121. data/ext/carray_utils.c +579 -113
  122. data/ext/extconf.rb +27 -2
  123. data/ext/mk_call_cfunc.rb +19 -4
  124. data/ext/mk_pow5_table.rb +35 -0
  125. data/ext/mkkernel.rb +1202 -1014
  126. data/ext/ruby_carray.c +17 -5
  127. data/ext/version.h +4 -4
  128. data/ext/version.rb +4 -2
  129. data/lib/carray/attribute.rb +160 -17
  130. data/lib/carray/autoload_carray.rb +23 -27
  131. data/lib/carray/autoload_method_extension.rb +20 -5
  132. data/lib/carray/axis_group.rb +360 -63
  133. data/lib/carray/basics.rb +162 -28
  134. data/lib/carray/bincount_nd.rb +18 -2
  135. data/lib/carray/block_iterator.rb +231 -58
  136. data/lib/carray/boolean_reduce.rb +34 -42
  137. data/lib/carray/categorical.rb +242 -52
  138. data/lib/carray/categorical_iterator.rb +437 -827
  139. data/lib/carray/complex.rb +41 -34
  140. data/lib/carray/conditional.rb +150 -14
  141. data/lib/carray/const_string.rb +59 -4
  142. data/lib/carray/construct.rb +15 -11
  143. data/lib/carray/data_type_extension.rb +1 -0
  144. data/lib/carray/distance_limit.rb +77 -0
  145. data/lib/carray/fixlen_string.rb +7 -1
  146. data/lib/carray/frame/column_verbs.rb +70 -0
  147. data/lib/carray/frame/concat.rb +157 -6
  148. data/lib/carray/frame/convert.rb +82 -7
  149. data/lib/carray/frame/csv_parser.rb +688 -87
  150. data/lib/carray/frame/describe.rb +80 -0
  151. data/lib/carray/frame/frame.rb +369 -45
  152. data/lib/carray/frame/group.rb +631 -90
  153. data/lib/carray/frame/io.rb +506 -79
  154. data/lib/carray/frame/join.rb +411 -62
  155. data/lib/carray/frame/records.rb +116 -19
  156. data/lib/carray/frame/reshape.rb +455 -0
  157. data/lib/carray/frame/sort.rb +30 -18
  158. data/lib/carray/frame/verbs.rb +868 -61
  159. data/lib/carray/frame/windows.rb +117 -0
  160. data/lib/carray/frame.rb +4 -0
  161. data/lib/carray/fuse_source.rb +118 -2
  162. data/lib/carray/fusion.rb +257 -42
  163. data/lib/carray/histogram.rb +6 -1
  164. data/lib/carray/inspect.rb +54 -13
  165. data/lib/carray/iterator.rb +99 -9
  166. data/lib/carray/key_windows.rb +620 -0
  167. data/lib/carray/lazy.rb +216 -193
  168. data/lib/carray/mask_gap_fill.rb +112 -92
  169. data/lib/carray/math.rb +32 -19
  170. data/lib/carray/meld_reduce.rb +52 -21
  171. data/lib/carray/methods/align_addr.rb +8 -4
  172. data/lib/carray/methods/bin.rb +26 -18
  173. data/lib/carray/methods/bincount.rb +18 -2
  174. data/lib/carray/methods/bit_string.rb +15 -4
  175. data/lib/carray/methods/choose.rb +16 -3
  176. data/lib/carray/methods/composition.rb +18 -10
  177. data/lib/carray/methods/factorize.rb +2 -2
  178. data/lib/carray/methods/gather_nd.rb +25 -5
  179. data/lib/carray/methods/insert_block.rb +9 -14
  180. data/lib/carray/methods/is_between.rb +59 -0
  181. data/lib/carray/methods/is_in.rb +140 -10
  182. data/lib/carray/methods/join.rb +2 -7
  183. data/lib/carray/methods/lag.rb +49 -0
  184. data/lib/carray/methods/locate_addr.rb +182 -45
  185. data/lib/carray/methods/mode.rb +18 -1
  186. data/lib/carray/methods/nunique.rb +11 -6
  187. data/lib/carray/methods/repeat.rb +1 -20
  188. data/lib/carray/methods/resize.rb +21 -23
  189. data/lib/carray/methods/snap.rb +34 -12
  190. data/lib/carray/methods/unique.rb +11 -2
  191. data/lib/carray/methods/value_counts.rb +4 -11
  192. data/lib/carray/record_order.rb +208 -0
  193. data/lib/carray/rng.rb +3 -0
  194. data/lib/carray/runtime.rb +65 -17
  195. data/lib/carray/segment_iterator.rb +1221 -0
  196. data/lib/carray/serialize.rb +214 -30
  197. data/lib/carray/slab_iterator.rb +101 -27
  198. data/lib/carray/stack.rb +10 -4
  199. data/lib/carray/string.rb +105 -5
  200. data/lib/carray/string_operation_extension.rb +12 -4
  201. data/lib/carray/struct.rb +137 -58
  202. data/lib/carray/struct_builder.rb +124 -17
  203. data/lib/carray/time.rb +1222 -186
  204. data/lib/carray/window_iterator.rb +242 -81
  205. data/lib/carray.rb +9 -2
  206. data/yard-stubs/ca_obj_array.rb +4 -4
  207. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  208. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  209. data/yard-stubs/ca_obj_fake.rb +1 -1
  210. data/yard-stubs/ca_obj_field.rb +5 -1
  211. data/yard-stubs/ca_obj_grid.rb +21 -10
  212. data/yard-stubs/ca_obj_refer.rb +5 -4
  213. data/yard-stubs/ca_obj_roll.rb +2 -3
  214. data/yard-stubs/ca_obj_shift.rb +5 -1
  215. data/yard-stubs/ca_obj_stride.rb +10 -7
  216. data/yard-stubs/ca_obj_tile.rb +1 -1
  217. data/yard-stubs/ca_obj_transpose.rb +1 -1
  218. data/yard-stubs/ca_obj_window.rb +9 -5
  219. data/yard-stubs/carray_access.rb +11 -5
  220. data/yard-stubs/carray_attribute.rb +0 -5
  221. data/yard-stubs/carray_broadcast.rb +1 -1
  222. data/yard-stubs/carray_cast.rb +88 -43
  223. data/yard-stubs/carray_conversion.rb +15 -2
  224. data/yard-stubs/carray_copy.rb +12 -5
  225. data/yard-stubs/carray_core.rb +3 -3
  226. data/yard-stubs/carray_count.rb +9 -0
  227. data/yard-stubs/carray_element.rb +10 -4
  228. data/yard-stubs/carray_generate.rb +21 -1
  229. data/yard-stubs/carray_lazy.rb +5 -1
  230. data/yard-stubs/carray_loop.rb +4 -1
  231. data/yard-stubs/carray_mask.rb +60 -12
  232. data/yard-stubs/carray_math.rb +52 -14
  233. data/yard-stubs/carray_median_percentile.rb +15 -8
  234. data/yard-stubs/carray_memory_view.rb +40 -23
  235. data/yard-stubs/carray_order.rb +61 -29
  236. data/yard-stubs/carray_random.rb +13 -4
  237. data/yard-stubs/carray_scatter.rb +21 -9
  238. data/yard-stubs/carray_segment.rb +72 -0
  239. data/yard-stubs/carray_slab.rb +10 -2
  240. data/yard-stubs/carray_sort.rb +49 -8
  241. data/yard-stubs/carray_test.rb +15 -3
  242. data/yard-stubs/carray_utils.rb +0 -26
  243. metadata +19 -5
  244. data/ext/ca_for_each_element.h +0 -239
  245. data/ext/carray_kernels_map.c +0 -3467
  246. data/lib/carray/arrow_tensor.rb +0 -401
  247. data/lib/carray/table.rb +0 -108
data/ext/ca_obj_face.c CHANGED
@@ -19,16 +19,18 @@
19
19
  ca_face_lift re-wrap a view as a Face when the source was
20
20
  ca_strip_face walk down through Face parents to storage
21
21
  CAFace abstract class organisational relay in the class hierarchy
22
- storage_to_scalar / per-obj_type dispatch for element decode (read)
23
- scalar_to_storage and encode (write) between surface and storage
22
+ storage_to_element / per-obj_type dispatch for element decode (read)
23
+ element_to_storage and encode (write) between surface and storage
24
24
  state-compatible / homogeneity + portability registries used by
25
- state-portable promote_list / CAStack / MV producer
25
+ state-portable promote_list / CAStack
26
+ memory_view which Faces the MemoryView producer exports
26
27
 
27
28
  ---------------------------------------------------------------------------- */
28
29
 
29
30
  #include "ruby.h"
30
31
  #include "carray.h"
31
32
  #include "ca_obj_face.h"
33
+ #include "carray_internal.h"
32
34
 
33
35
  /* ---- shared op helpers (thin-forward) ---- */
34
36
 
@@ -37,7 +39,7 @@ ca_face_attach (void *ap)
37
39
  {
38
40
  CAView *cav = (CAView *) ap;
39
41
  CArray *ca = (CArray *) ap;
40
- ca_attach(cav->parent);
42
+ ca_attach(cav->parent); /* window: a Face's attach slot; its detach slot releases */
41
43
  /* alias parent->ptr (Face has identical data layout, so copy is unneeded) */
42
44
  ca->ptr = cav->parent->ptr;
43
45
  }
@@ -100,6 +102,7 @@ void
100
102
  ca_face_xfer_stride (void *ap, ca_size_t *starts, ca_size_t *counts,
101
103
  ca_size_t *strides, void *data, int dir)
102
104
  {
105
+ if ( ca_region_is_empty(((CArray *) ap)->ndim, counts) ) return;
103
106
  CAView *cav = (CAView *) ap;
104
107
  ca_xfer_stride(cav->parent, starts, counts, strides, data, dir);
105
108
  }
@@ -129,10 +132,18 @@ rb_ca_face_template (VALUE original_face, CArray *new_parent,
129
132
  TypedData_Get_Struct(original_face, CArray, &carray_data_type, src);
130
133
  tdata = ca_typeddata[src->obj_type];
131
134
 
132
- /* Actual subclass size (incl. tail) — via TypedData's dsize callback */
133
- size = (tdata && tdata->function.dsize)
134
- ? tdata->function.dsize(src)
135
- : sizeof(CAView);
135
+ /* The struct with its subclass tail. struct_size is the struct alone;
136
+ a dsize callback also counts the buffers the struct points to (dim),
137
+ so it is only the fallback for a type registered without struct_size,
138
+ and copying that many bytes reads past the struct. */
139
+ if ( ca_func[src->obj_type].struct_size ) {
140
+ size = ca_func[src->obj_type].struct_size;
141
+ }
142
+ else {
143
+ size = (tdata && tdata->function.dsize)
144
+ ? tdata->function.dsize(src)
145
+ : sizeof(CAView);
146
+ }
136
147
 
137
148
  dst = (CArray *) xmalloc(size);
138
149
  /* CAREFUL: the whole-struct memcpy is how a C-tail Face
@@ -174,6 +185,44 @@ rb_ca_face_template (VALUE original_face, CArray *new_parent,
174
185
  dst);
175
186
  }
176
187
 
188
+ VALUE
189
+ ca_face_relift_entity (VALUE face, VALUE result)
190
+ {
191
+ static ID id_copy_state = 0;
192
+ CArray *rc, *lc;
193
+ VALUE lifted;
194
+ if ( id_copy_state == 0 ) id_copy_state = rb_intern("copy_state");
195
+ TypedData_Get_Struct(result, CArray, &carray_data_type, rc);
196
+ lifted = rb_ca_face_template(face, rc, rc->dim);
197
+ rb_ca_set_parent(lifted, result);
198
+ if ( ca_has_mask(rc) ) {
199
+ /* The template starts without a mask. */
200
+ TypedData_Get_Struct(lifted, CArray, &carray_data_type, lc);
201
+ ca_copy_mask(lc, rc);
202
+ }
203
+ if ( rb_respond_to(face, id_copy_state) ) {
204
+ rb_funcall(lifted, id_copy_state, 1, face);
205
+ }
206
+ return lifted;
207
+ }
208
+
209
+ /* A lift moves a view built on a wrapper (a Face or the lazy marker) one
210
+ * level down, onto what the wrapper wraps, so the view no longer keeps the
211
+ * wrapper alive. The view's mask was built over the wrapper's mask, which
212
+ * the wrapper owns and frees with itself; move it down the same level.
213
+ * The wrapper's mask is a same-shape refer of its parent's mask, so the
214
+ * mask view addresses the same cells there. */
215
+ static void
216
+ ca_lift_move_mask_down (CArray *view_ca, CArray *wp)
217
+ {
218
+ CArray *below = ((CAView *) wp)->parent;
219
+ if ( view_ca->mask && wp->mask && ca_is_view(wp->mask)
220
+ && ((CAView *) wp->mask)->parent == below->mask
221
+ && ((CAView *) view_ca->mask)->parent == wp->mask ) {
222
+ ((CAView *) view_ca->mask)->parent = below->mask;
223
+ }
224
+ }
225
+
177
226
  /* ca_face_lift(view, face_parent) — at the tail of user-facing entry
178
227
  * points (`ca[...]` etc.), when the operand was a Face, re-wrap the
179
228
  * resulting view as a Face of the same subclass. Thin wrapper over
@@ -236,6 +285,7 @@ ca_face_lift (VALUE view, VALUE face_parent)
236
285
  && ! ca_test_flag(view_ca, CA_FLAG_MULTI_PARENTS) ) {
237
286
  ((CAView *) view_ca)->parent = ((CAView *) fp)->parent; /* C pointer: one level */
238
287
  rb_ca_set_parent(view, rb_ca_parent(face_parent)); /* @parent ivar: lockstep */
288
+ ca_lift_move_mask_down(view_ca, fp);
239
289
  }
240
290
  }
241
291
 
@@ -245,6 +295,12 @@ ca_face_lift (VALUE view, VALUE face_parent)
245
295
  lifted = rb_ca_face_template(face_parent, view_ca, view_ca->dim);
246
296
  /* Pin view as a GC root of lifted so the slice is not collected. */
247
297
  rb_ca_set_parent(lifted, view);
298
+ /* The Face the view was taken from is off the parent chain now; keep it
299
+ for the attributes set on it. A fresh result the ordering kernels
300
+ re-wrap is not a view of it and takes none. */
301
+ if ( ca_is_view(view_ca) ) {
302
+ rb_ca_set_lifted_from(lifted, face_parent);
303
+ }
248
304
 
249
305
  /* Optional Ruby callback for ivar / state carry. Faces holding
250
306
  semantic state in the struct tail (CATime / CATimedelta
@@ -322,6 +378,7 @@ ca_lazy_marker_lift (VALUE view, VALUE marker)
322
378
  && ! ca_test_flag(view_ca, CA_FLAG_MULTI_PARENTS) ) {
323
379
  ((CAView *) view_ca)->parent = ((CAView *) mp)->parent; /* C pointer */
324
380
  rb_ca_set_parent(view, rb_ca_parent(marker)); /* @parent ivar */
381
+ ca_lift_move_mask_down(view_ca, mp);
325
382
  }
326
383
 
327
384
  return rb_ca_lazy_marker_new(view);
@@ -419,37 +476,62 @@ rb_ca_face_s_allocate_forbidden (VALUE klass)
419
476
  "(CATime / CATimedelta / ...)");
420
477
  }
421
478
 
422
- /* Face-local C dispatch table for storage_to_scalar (the read/decode
479
+ VALUE
480
+ ca_face_storage_value (CArray *ca, VALUE raw)
481
+ {
482
+ CArray *storage;
483
+ CArray as_storage;
484
+ if ( ca->data_type != CA_FIXLEN || TYPE(raw) != T_STRING ) {
485
+ return raw;
486
+ }
487
+ storage = ca_strip_face(ca);
488
+ if ( storage == NULL
489
+ || storage->data_type == CA_FIXLEN
490
+ || ca_is_object_type(storage)
491
+ || RSTRING_LEN(raw) != storage->bytes ) {
492
+ return raw;
493
+ }
494
+ if ( storage->data_type == CA_BOOLEAN ) {
495
+ return *(uint8_t *) RSTRING_PTR(raw) ? Qtrue : Qfalse;
496
+ }
497
+ /* The cell lives in a String's buffer, not in the storage: read it
498
+ through a stand-in that carries only the storage's type. */
499
+ as_storage.data_type = storage->data_type;
500
+ as_storage.bytes = storage->bytes;
501
+ return ca_ptr2obj(&as_storage, RSTRING_PTR(raw));
502
+ }
503
+
504
+ /* Face-local C dispatch table for storage_to_element (the read/decode
423
505
  direction). Sized to CA_OBJ_TYPE_MAX so any registered obj_type can be
424
506
  indexed directly; file-scope zero-init leaves unregistered slots NULL. */
425
- ca_face_storage_to_scalar_fn ca_face_storage_to_scalar_table[CA_OBJ_TYPE_MAX];
507
+ ca_face_storage_to_element_fn ca_face_storage_to_element_table[CA_OBJ_TYPE_MAX];
426
508
 
427
509
  void
428
- ca_face_register_storage_to_scalar (int obj_type, ca_face_storage_to_scalar_fn fn)
510
+ ca_face_register_storage_to_element (int obj_type, ca_face_storage_to_element_fn fn)
429
511
  {
430
512
  if ( obj_type < 0 || obj_type >= CA_OBJ_TYPE_MAX ) {
431
513
  rb_raise(rb_eArgError,
432
- "ca_face_register_storage_to_scalar: obj_type %d out of range",
514
+ "ca_face_register_storage_to_element: obj_type %d out of range",
433
515
  obj_type);
434
516
  }
435
- ca_face_storage_to_scalar_table[obj_type] = fn;
517
+ ca_face_storage_to_element_table[obj_type] = fn;
436
518
  }
437
519
 
438
- /* Face-local C dispatch table for scalar_to_storage (the write/encode
520
+ /* Face-local C dispatch table for element_to_storage (the write/encode
439
521
  direction), the mirror of the decode table above. Unregistered slots
440
522
  stay NULL; a Face without a C fast path relies on the Ruby fallback in
441
- ca_face_scalar_to_storage (carray_cast.c). */
442
- ca_face_scalar_to_storage_fn ca_face_scalar_to_storage_table[CA_OBJ_TYPE_MAX];
523
+ ca_face_element_to_storage (carray_cast.c). */
524
+ ca_face_element_to_storage_fn ca_face_element_to_storage_table[CA_OBJ_TYPE_MAX];
443
525
 
444
526
  void
445
- ca_face_register_scalar_to_storage (int obj_type, ca_face_scalar_to_storage_fn fn)
527
+ ca_face_register_element_to_storage (int obj_type, ca_face_element_to_storage_fn fn)
446
528
  {
447
529
  if ( obj_type < 0 || obj_type >= CA_OBJ_TYPE_MAX ) {
448
530
  rb_raise(rb_eArgError,
449
- "ca_face_register_scalar_to_storage: obj_type %d out of range",
531
+ "ca_face_register_element_to_storage: obj_type %d out of range",
450
532
  obj_type);
451
533
  }
452
- ca_face_scalar_to_storage_table[obj_type] = fn;
534
+ ca_face_element_to_storage_table[obj_type] = fn;
453
535
  }
454
536
 
455
537
  /* -- Face state homogeneity check -- */
@@ -473,15 +555,10 @@ ca_face_register_state_compatible (int obj_type, ca_face_state_compatible_fn fn)
473
555
  static int
474
556
  ca_face_has_ruby_state_check (VALUE va)
475
557
  {
476
- static ID id_face_state_compatible_p = 0;
477
- static ID id_method = 0;
478
- static ID id_owner = 0;
558
+ ID id_face_state_compatible_p = rb_intern("face_state_compatible?");
559
+ ID id_method = rb_intern("method");
560
+ ID id_owner = rb_intern("owner");
479
561
  VALUE m, owner;
480
- if ( id_face_state_compatible_p == 0 ) {
481
- id_face_state_compatible_p = rb_intern("face_state_compatible?");
482
- id_method = rb_intern("method");
483
- id_owner = rb_intern("owner");
484
- }
485
562
  m = rb_funcall(va, id_method, 1, ID2SYM(id_face_state_compatible_p));
486
563
  owner = rb_funcall(m, id_owner, 0);
487
564
  return ( owner != rb_cCArray ) ? 1 : 0;
@@ -524,6 +601,31 @@ rb_ca_face_state_compatible_p (VALUE self, VALUE other)
524
601
  return ca_face_state_compatible(self, a, other, b) ? Qtrue : Qfalse;
525
602
  }
526
603
 
604
+ /* -- Face MemoryView export -- */
605
+
606
+ /* A Face is exported through MemoryView only when its storage bytes carry
607
+ its values without the Face (a struct record, fixed-width bytes). Zero
608
+ (the default) refuses the export: the consumer would see the storage
609
+ with nothing to say what it means. */
610
+ static int8_t ca_face_memory_view_table[CA_OBJ_TYPE_MAX];
611
+
612
+ void
613
+ ca_face_register_memory_view (int obj_type, int exportable)
614
+ {
615
+ if ( obj_type < 0 || obj_type >= CA_OBJ_TYPE_MAX ) {
616
+ rb_raise(rb_eArgError,
617
+ "ca_face_register_memory_view: obj_type %d out of range",
618
+ obj_type);
619
+ }
620
+ ca_face_memory_view_table[obj_type] = exportable ? 1 : 0;
621
+ }
622
+
623
+ int
624
+ ca_face_memory_view_exportable (CArray *ca)
625
+ {
626
+ return ca_face_memory_view_table[ca->obj_type] == 1;
627
+ }
628
+
527
629
  /* -- Face state portability -- */
528
630
 
529
631
  /* int8_t per slot to distinguish "unregistered" (-1, dispatch falls
@@ -576,16 +678,11 @@ ca_face_register_state_portable (int obj_type, int portable)
576
678
  static int
577
679
  ca_face_class_has_portable_method (VALUE klass)
578
680
  {
579
- static ID id_face_state_portable_p = 0;
580
- static ID id_method = 0;
581
- static ID id_owner = 0;
681
+ ID id_face_state_portable_p = rb_intern("face_state_portable?");
682
+ ID id_method = rb_intern("method");
683
+ ID id_owner = rb_intern("owner");
582
684
  VALUE m, owner;
583
685
  if ( NIL_P(klass) ) return 0;
584
- if ( id_face_state_portable_p == 0 ) {
585
- id_face_state_portable_p = rb_intern("face_state_portable?");
586
- id_method = rb_intern("method");
587
- id_owner = rb_intern("owner");
588
- }
589
686
  if ( !rb_respond_to(klass, id_face_state_portable_p) ) return 0;
590
687
  m = rb_funcall(klass, id_method, 1, ID2SYM(id_face_state_portable_p));
591
688
  owner = rb_funcall(m, id_owner, 0);
@@ -595,6 +692,132 @@ ca_face_class_has_portable_method (VALUE klass)
595
692
  return ( owner != rb_singleton_class(rb_cCArray) ) ? 1 : 0;
596
693
  }
597
694
 
695
+ /* The conversion a refusal of a Face whose cells encode its values points
696
+ at: a string Face gives its strings with #to_string, any other its values
697
+ with to_type(:object). */
698
+ const char *
699
+ ca_face_conversion_hint (VALUE face)
700
+ {
701
+ return rb_respond_to(face, rb_intern("to_string"))
702
+ ? "#to_string gives its strings"
703
+ : "to_type(:object) gives its values";
704
+ }
705
+
706
+ /* The method that gives the storage under a Face, for a refusal to point
707
+ at: #ticks for a time or a duration, #codes for a categorical. NULL for
708
+ a Face that names its storage some other way, or not at all. */
709
+ const char *
710
+ ca_face_storage_method (VALUE face)
711
+ {
712
+ static const char *names[] = { "ticks", "codes" };
713
+ size_t i;
714
+ for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) {
715
+ if ( rb_respond_to(face, rb_intern(names[i])) ) {
716
+ return names[i];
717
+ }
718
+ }
719
+ return NULL;
720
+ }
721
+
722
+ /* ca.face_storage_method -- the name of the method that gives the storage
723
+ under ca (ca_face_storage_method), as a String, or nil. For refusals
724
+ written in Ruby. */
725
+ static VALUE
726
+ rb_ca_face_storage_method (VALUE self)
727
+ {
728
+ const char *name = ca_face_storage_method(self);
729
+ return name ? rb_str_new_cstr(name) : Qnil;
730
+ }
731
+
732
+ /* Refuse to order (or, with verb "count", to count by value) a Face whose
733
+ storage does not hold its values in order, pointing at what does: its
734
+ values, or its storage when that is what is meant. */
735
+ void
736
+ ca_face_refuse_order (const char *name, VALUE face, const char *verb)
737
+ {
738
+ const char *storage = ca_face_storage_method(face);
739
+ if ( storage ) {
740
+ rb_raise(rb_eArgError,
741
+ "%s: %s is not orderable by storage (a cell encodes the value "
742
+ "rather than being it); %s its values instead -- %s -- or %s "
743
+ "its storage, which #%s gives",
744
+ name, rb_obj_classname(face), verb,
745
+ ca_face_conversion_hint(face), verb, storage);
746
+ }
747
+ rb_raise(rb_eArgError,
748
+ "%s: %s is not orderable by storage (a cell encodes the value "
749
+ "rather than being it); %s its values instead -- %s",
750
+ name, rb_obj_classname(face), verb,
751
+ ca_face_conversion_hint(face));
752
+ }
753
+
754
+ /* The rule every family that orders, compares, searches or hashes the cells
755
+ of a Face reference keeps: the storage stands for the values only when the
756
+ Face declares ORDERABLE_STORAGE. A fixlen storage is no exception -- its
757
+ bytes are an order only when the Face says so (CAFixlenString). Returns
758
+ when the Face is ORDERABLE; refuses otherwise (ca_face_refuse_order). A
759
+ family that can order by a key asks ca_face_orders_by_itself first. */
760
+ void
761
+ ca_face_require_orderable (VALUE face, const char *name, const char *verb)
762
+ {
763
+ CArray *ca;
764
+ TypedData_Get_Struct(face, CArray, &carray_data_type, ca);
765
+ if ( ! ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
766
+ ca_face_refuse_order(name, face, verb);
767
+ }
768
+ }
769
+
770
+ /* Whether a Face is ordered by its own #order rather than by its storage:
771
+ it is not ORDERABLE and answers #order itself (CAConstString, CARecord),
772
+ so a family that orders by a key can take that order. CArray#order is
773
+ not one: it sorts the storage and comes back to the gate. */
774
+ int
775
+ ca_face_orders_by_itself (VALUE face)
776
+ {
777
+ ID id_order = rb_intern("order");
778
+ ID id_method = rb_intern("method");
779
+ ID id_owner = rb_intern("owner");
780
+ CArray *ca;
781
+ VALUE m;
782
+ TypedData_Get_Struct(face, CArray, &carray_data_type, ca);
783
+ if ( ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
784
+ return 0;
785
+ }
786
+ m = rb_funcall(face, id_method, 1, ID2SYM(id_order));
787
+ return ( rb_funcall(m, id_owner, 0) != rb_cCArray ) ? 1 : 0;
788
+ }
789
+
790
+ /* Refuse a numeric operation (arithmetic, math, a reduction) on a Face,
791
+ whose cells encode its values rather than being numbers, pointing at what
792
+ gives numbers. Returns when `self` is not a Face. */
793
+ void
794
+ ca_face_refuse_numeric (VALUE self, const char *name, VALUE eclass)
795
+ {
796
+ CArray *ca;
797
+ if ( ! rb_obj_is_carray(self) ) {
798
+ return;
799
+ }
800
+ GetCArray(self, ca);
801
+ if ( ! ca_is_face(ca) ) {
802
+ return;
803
+ }
804
+ {
805
+ const char *storage = ca_face_storage_method(self);
806
+ if ( storage ) {
807
+ rb_raise(eclass,
808
+ "%s: %s does not define it (a cell encodes the value rather "
809
+ "than being a number); compute on its values instead -- %s -- "
810
+ "or on its storage, which #%s gives",
811
+ name, rb_obj_classname(self), ca_face_conversion_hint(self),
812
+ storage);
813
+ }
814
+ }
815
+ rb_raise(eclass,
816
+ "%s: %s does not define it (a cell encodes the value rather than "
817
+ "being a number); compute on its values instead -- %s",
818
+ name, rb_obj_classname(self), ca_face_conversion_hint(self));
819
+ }
820
+
598
821
  VALUE
599
822
  ca_face_operand_descend (VALUE operand, const char *name)
600
823
  {
@@ -609,16 +832,203 @@ ca_face_operand_descend (VALUE operand, const char *name)
609
832
  if ( ca_test_flag(op, CA_FLAG_FACE_COMPARABLE_STORAGE) ) {
610
833
  return rb_ca_strip_face_value(operand);
611
834
  }
835
+ {
836
+ const char *storage = ca_face_storage_method(operand);
837
+ if ( storage ) {
838
+ rb_raise(rb_eArgError,
839
+ "%s: cannot take a %s operand: its storage is not its surface "
840
+ "(a cell encodes the value rather than being it), so comparing "
841
+ "the storage would compare the encoding. Convert the operand "
842
+ "to the receiver's space first -- %s -- or compare the storage "
843
+ "of both sides, which #%s gives",
844
+ name, rb_obj_classname(operand),
845
+ ca_face_conversion_hint(operand), storage);
846
+ }
847
+ }
612
848
  rb_raise(rb_eArgError,
613
849
  "%s: cannot take a %s operand: its storage is not its surface "
614
850
  "(a cell encodes the value rather than being it), so comparing "
615
851
  "the storage would compare the encoding. Convert the operand to "
616
- "the receiver's space first -- #to_string for a string Face -- "
617
- "or pass .parent on both sides to work in storage space",
618
- name, rb_obj_classname(operand));
852
+ "the receiver's space first -- %s",
853
+ name, rb_obj_classname(operand), ca_face_conversion_hint(operand));
854
+ return Qnil; /* not reached */
855
+ }
856
+
857
+ /* Whether two arrays, at least one a Face other than a record array, are
858
+ equal (`==`, strict = 0) or the same key (`eql?`, strict = 1).
859
+
860
+ A Face equals only a Face of its own class. When the two share their
861
+ state (a time array's unit) and the storage holds the values in their
862
+ order, equal storage is equal values, so the storage is compared.
863
+ Otherwise `==` compares the values themselves (two time arrays of
864
+ different units, two const strings over different pools, two
865
+ categoricals with differently ordered labels), and `eql?`, which has to
866
+ agree with #hash, answers false. */
867
+ int
868
+ ca_face_equal (VALUE va, VALUE vb, int strict)
869
+ {
870
+ CArray *a, *b;
871
+ int8_t i;
872
+ TypedData_Get_Struct(va, CArray, &carray_data_type, a);
873
+ TypedData_Get_Struct(vb, CArray, &carray_data_type, b);
874
+ if ( ! ca_is_face(a) || ! ca_is_face(b) ) {
875
+ return 0;
876
+ }
877
+ if ( rb_obj_class(va) != rb_obj_class(vb) ) {
878
+ return 0;
879
+ }
880
+ if ( a->ndim != b->ndim ) {
881
+ return 0;
882
+ }
883
+ for (i = 0; i < a->ndim; i++) {
884
+ if ( a->dim[i] != b->dim[i] ) {
885
+ return 0;
886
+ }
887
+ }
888
+ if ( ca_face_state_compatible(va, a, vb, b)
889
+ && ( strict || ca_test_flag(a, CA_FLAG_FACE_ORDERABLE_STORAGE) ) ) {
890
+ return strict ? ca_eql_storage(ca_strip_face(a), ca_strip_face(b))
891
+ : ca_equal(ca_strip_face(a), ca_strip_face(b));
892
+ }
893
+ if ( strict ) {
894
+ return 0;
895
+ }
896
+ return RTEST(rb_equal(rb_funcall(va, rb_intern("to_a"), 0),
897
+ rb_funcall(vb, rb_intern("to_a"), 0)));
898
+ }
899
+
900
+ /* An operand Face whose storage is not even in the order of its values
901
+ (a CAConstString, a CACategorical) is read by its values: its strings
902
+ (#to_string) or, for any other, its values (to_type(:object)). Any
903
+ other operand is returned as it is. */
904
+ static VALUE
905
+ ca_face_operand_values (VALUE operand)
906
+ {
907
+ CArray *op;
908
+ if ( ! RTEST(rb_obj_is_carray(operand)) ) {
909
+ return operand;
910
+ }
911
+ TypedData_Get_Struct(operand, CArray, &carray_data_type, op);
912
+ if ( ! ca_is_face(op) || op->obj_type == CA_OBJ_RECORD
913
+ || ca_test_flag(op, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
914
+ return operand;
915
+ }
916
+ if ( rb_respond_to(operand, rb_intern("to_string")) ) {
917
+ return rb_funcall(operand, rb_intern("to_string"), 0);
918
+ }
919
+ return rb_funcall(operand, rb_intern("to_type"), 1, INT2NUM(CA_OBJECT));
920
+ }
921
+
922
+ /* Bring `operand` into the storage space of `reference`, a Face, so the
923
+ two can be compared, searched, counted or hashed cell by cell. Every
924
+ family that takes an operand against a Face goes through here; the
925
+ family keeps its own gate on the reference (ORDERABLE, a record's own
926
+ rules) and its own handling of a scalar operand afterwards.
927
+
928
+ - A record reference takes records of its own data_class (stripped to
929
+ their bytes) and plain operands as they are; any other Face operand is
930
+ refused with TypeError.
931
+ - A COMPARABLE reference stores its values as they are, so a plain
932
+ operand is taken as it is, a Face whose cells are its values is
933
+ stripped, a string or categorical Face is read by its values, and a
934
+ Face whose cells encode its values in order (a time) is refused
935
+ (ca_face_operand_descend).
936
+ - A reference whose storage is not in the order of its values refuses,
937
+ naming the conversion that gives its values.
938
+ - A reference with to_comparable brings any operand into its own space
939
+ (a unit, an instant), and refuses what it cannot convert.
940
+ - Any other reference has no way to read the operand in its space, and
941
+ refuses it.
942
+ UNDEF is undefined in every space, so it passes as it is and the entry
943
+ answers it as it does for a plain array. */
944
+ VALUE
945
+ ca_face_reconcile_operand (VALUE reference, VALUE operand, const char *name)
946
+ {
947
+ CArray *ca;
948
+ if ( operand == CA_UNDEF ) {
949
+ return operand;
950
+ }
951
+ TypedData_Get_Struct(reference, CArray, &carray_data_type, ca);
952
+ if ( ca->obj_type == CA_OBJ_RECORD ) {
953
+ if ( RTEST(rb_obj_is_carray(operand)) ) {
954
+ CArray *op;
955
+ TypedData_Get_Struct(operand, CArray, &carray_data_type, op);
956
+ if ( ca_is_face(op) ) {
957
+ if ( op->obj_type != CA_OBJ_RECORD ) {
958
+ rb_raise(rb_eTypeError,
959
+ "%s: can not take a %s operand for a record array of "
960
+ "%"PRIsVALUE, name, rb_obj_classname(operand),
961
+ rb_ca_data_class(reference));
962
+ }
963
+ if ( rb_ca_data_class(operand) != rb_ca_data_class(reference) ) {
964
+ rb_raise(rb_eTypeError,
965
+ "%s: can not take a record array of %"PRIsVALUE
966
+ " for a record array of %"PRIsVALUE, name,
967
+ rb_ca_data_class(operand), rb_ca_data_class(reference));
968
+ }
969
+ return rb_ca_strip_face_value(operand);
970
+ }
971
+ }
972
+ return operand;
973
+ }
974
+ if ( ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE) ) {
975
+ return ca_face_operand_descend(ca_face_operand_values(operand), name);
976
+ }
977
+ ca_face_require_orderable(reference, name, "compare");
978
+ if ( rb_respond_to(reference, rb_intern("to_comparable")) ) {
979
+ return rb_ca_strip_face_value(rb_funcall(reference,
980
+ rb_intern("to_comparable"),
981
+ 1, operand));
982
+ }
983
+ {
984
+ const char *storage = ca_face_storage_method(reference);
985
+ if ( storage ) {
986
+ rb_raise(rb_eArgError,
987
+ "%s: %s is not comparable by storage and has no to_comparable "
988
+ "to bring the operand into its space; compare the storage of "
989
+ "both sides, which #%s gives",
990
+ name, rb_obj_classname(reference), storage);
991
+ }
992
+ }
993
+ rb_raise(rb_eArgError,
994
+ "%s: %s is not comparable by storage and has no to_comparable "
995
+ "to bring the operand into its space",
996
+ name, rb_obj_classname(reference));
619
997
  return Qnil; /* not reached */
620
998
  }
621
999
 
1000
+ /* The reference and the operand of a comparison (an operator, count(v), a
1001
+ search, linear_section) brought into one space. A time reference answers
1002
+ both through comparable_pair: it keeps itself when the operand sits on its
1003
+ grid, and otherwise meets the operand in the finer unit the two share,
1004
+ where to_comparable would refuse a value off its grid. Every other
1005
+ reference keeps itself and reconciles the operand
1006
+ (ca_face_reconcile_operand). An assignment, which has to land on the
1007
+ reference's grid, uses ca_face_reconcile_operand. */
1008
+ void
1009
+ ca_face_reconcile_pair (volatile VALUE *pref, volatile VALUE *poperand,
1010
+ const char *name)
1011
+ {
1012
+ static ID id_comparable_pair = 0;
1013
+ VALUE ref = *pref, operand = *poperand;
1014
+ CArray *ca;
1015
+ if ( id_comparable_pair == 0 ) {
1016
+ id_comparable_pair = rb_intern("comparable_pair");
1017
+ }
1018
+ TypedData_Get_Struct(ref, CArray, &carray_data_type, ca);
1019
+ if ( operand != CA_UNDEF
1020
+ && ca->obj_type != CA_OBJ_RECORD
1021
+ && ! ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE)
1022
+ && ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE)
1023
+ && rb_respond_to(ref, id_comparable_pair) ) {
1024
+ VALUE pair = rb_funcall(ref, id_comparable_pair, 1, operand);
1025
+ *pref = rb_ary_entry(pair, 0);
1026
+ *poperand = rb_ca_strip_face_value(rb_ary_entry(pair, 1));
1027
+ return;
1028
+ }
1029
+ *poperand = ca_face_reconcile_operand(ref, operand, name);
1030
+ }
1031
+
622
1032
  int
623
1033
  ca_face_state_portable (int obj_type, VALUE klass)
624
1034
  {
@@ -635,6 +1045,84 @@ ca_face_state_portable (int obj_type, VALUE klass)
635
1045
  return 1; /* default portable */
636
1046
  }
637
1047
 
1048
+ VALUE
1049
+ ca_face_list_class (VALUE list, const char *who)
1050
+ {
1051
+ long n = RARRAY_LEN(list), i;
1052
+ int any_face = 0, all_face = 1;
1053
+ VALUE face_class = Qnil, face = Qnil;
1054
+ CArray *ref = NULL;
1055
+
1056
+ for ( i = 0; i < n; i++ ) {
1057
+ VALUE p = rb_ary_entry(list, i);
1058
+ CArray *ca;
1059
+ rb_check_carray_object(p);
1060
+ TypedData_Get_Struct(p, CArray, &carray_data_type, ca);
1061
+ if ( ! ca_is_face(ca) ) { all_face = 0; continue; }
1062
+ any_face = 1;
1063
+ if ( NIL_P(face_class) ) {
1064
+ face_class = rb_obj_class(p);
1065
+ face = p;
1066
+ ref = ca;
1067
+ }
1068
+ else if ( rb_obj_class(p) != face_class ) {
1069
+ all_face = 0;
1070
+ }
1071
+ }
1072
+ if ( ! any_face ) return Qnil;
1073
+ if ( ! all_face ) {
1074
+ const char *storage = ca_face_storage_method(face);
1075
+ if ( ref->obj_type == CA_OBJ_STRING ) {
1076
+ rb_raise(rb_eArgError,
1077
+ "%s: cannot mix Face and non-Face arrays, or arrays of "
1078
+ "different Face classes; convert them first to one Face "
1079
+ "class or to no Face (CArray.string(a) makes a CAString of "
1080
+ "an object array of Strings)",
1081
+ who);
1082
+ }
1083
+ if ( storage ) {
1084
+ rb_raise(rb_eArgError,
1085
+ "%s: cannot mix Face and non-Face arrays, or arrays of "
1086
+ "different Face classes; convert them first to one Face "
1087
+ "class or to no Face, or combine their storage (#%s gives "
1088
+ "the storage of a %s)",
1089
+ who, storage, rb_obj_classname(face));
1090
+ }
1091
+ rb_raise(rb_eArgError,
1092
+ "%s: cannot mix Face and non-Face arrays, or arrays of "
1093
+ "different Face classes; convert them first to one Face "
1094
+ "class or to no Face",
1095
+ who);
1096
+ }
1097
+ if ( n > 1 && ! ca_face_state_portable(ref->obj_type, face_class) ) {
1098
+ rb_raise(rb_eArgError,
1099
+ "%s: %s state is not portable across several arrays (each "
1100
+ "keeps its values in storage of its own); convert them first "
1101
+ "(%s)",
1102
+ who, rb_class2name(face_class),
1103
+ ca_face_conversion_hint(face));
1104
+ }
1105
+ for ( i = 1; i < n; i++ ) {
1106
+ VALUE p = rb_ary_entry(list, i);
1107
+ CArray *ca;
1108
+ TypedData_Get_Struct(p, CArray, &carray_data_type, ca);
1109
+ if ( ! ca_face_state_compatible(rb_ary_entry(list, 0), ref, p, ca) ) {
1110
+ rb_raise(rb_eArgError,
1111
+ "%s: Face state mismatch (the %s at index %ld differs from "
1112
+ "index 0)", who, rb_class2name(face_class), i);
1113
+ }
1114
+ }
1115
+ return face_class;
1116
+ }
1117
+
1118
+ VALUE
1119
+ ca_face_strip_one (VALUE v)
1120
+ {
1121
+ CArray *c;
1122
+ TypedData_Get_Struct(v, CArray, &carray_data_type, c);
1123
+ return ca_is_face(c) ? rb_ca_parent(v) : v;
1124
+ }
1125
+
638
1126
  /* CArray.face_state_portable? — class method on CArray. Subclasses
639
1127
  (Face classes) override by defining their own `def self.face_state_portable?`.
640
1128
  This default returns true (= 1) for non-Face CArray classes; the actual
@@ -645,6 +1133,57 @@ rb_ca_s_face_state_portable_p (VALUE klass)
645
1133
  return Qtrue;
646
1134
  }
647
1135
 
1136
+ /* ca.orderable_storage? -- whether ca's storage holds its values, in
1137
+ their order, so that a lookup may compare the storage: true for an array
1138
+ that is not a Face and for a Face that declares
1139
+ CA_FLAG_FACE_ORDERABLE_STORAGE, false for a Face whose cells encode the
1140
+ value (a const string's ranges, a categorical's codes). */
1141
+ static VALUE
1142
+ rb_ca_orderable_storage_p (VALUE self)
1143
+ {
1144
+ CArray *ca;
1145
+ GetCArray(self, ca);
1146
+ if ( ! ca_is_face(ca) ) return Qtrue;
1147
+ return ca_test_flag(ca, CA_FLAG_FACE_ORDERABLE_STORAGE) ? Qtrue : Qfalse;
1148
+ }
1149
+
1150
+ /* ca.comparable_storage? -- whether ca's storage holds its values as
1151
+ they are, so that a plain operand may be compared with the storage: true
1152
+ for an array that is not a Face and for a Face that declares
1153
+ CA_FLAG_FACE_COMPARABLE_STORAGE. */
1154
+ static VALUE
1155
+ rb_ca_comparable_storage_p (VALUE self)
1156
+ {
1157
+ CArray *ca;
1158
+ GetCArray(self, ca);
1159
+ if ( ! ca_is_face(ca) ) return Qtrue;
1160
+ return ca_test_flag(ca, CA_FLAG_FACE_COMPARABLE_STORAGE) ? Qtrue : Qfalse;
1161
+ }
1162
+
1163
+ /* ca.face_storage -- the storage under a Face (ca itself when it is not
1164
+ a Face). */
1165
+ static VALUE
1166
+ rb_ca_face_storage (VALUE self)
1167
+ {
1168
+ return rb_ca_strip_face_value(self);
1169
+ }
1170
+
1171
+ /* ca.reconcile_operand(operand, name) -- operand brought into ca's
1172
+ storage space by the rule every operand family follows
1173
+ (ca_face_reconcile_operand for a Face, ca_face_operand_descend for an
1174
+ array that is not one). For the families written in Ruby. */
1175
+ static VALUE
1176
+ rb_ca_reconcile_operand (VALUE self, VALUE operand, VALUE rname)
1177
+ {
1178
+ CArray *ca;
1179
+ const char *name = StringValueCStr(rname);
1180
+ GetCArray(self, ca);
1181
+ if ( ca_is_face(ca) ) {
1182
+ return ca_face_reconcile_operand(self, operand, name);
1183
+ }
1184
+ return ca_face_operand_descend(operand, name);
1185
+ }
1186
+
648
1187
  /* CArray#face_lift(face_parent) — Ruby surface for ca_face_lift.
649
1188
  Used by CArray.stack to re-wrap a raw CAStack with the homogeneous
650
1189
  Face class carried by face_parent (typically list[0] after the
@@ -666,21 +1205,19 @@ rb_ca_face_lift_method (VALUE self, VALUE face_parent)
666
1205
 
667
1206
  /* ca_face_reconcile_comparison(pself, pother) — Face gate for the element-
668
1207
  * wise comparison operators (< <= > >=, eq, ne; <=> composes from > and <).
669
- * Mirrors the search query gate (PROPOSAL_FACE_ORDERING_GATE): when the LHS
1208
+ * Mirrors the search query gate (docs/authoring/FaceOrderingSearch.md): when the LHS
670
1209
  * is a Face over NUMERIC storage, memcmp on the surface fixlen bytes would
671
1210
  * mis-order the storage, so descend to storage (ORDERABLE licenses that the
672
1211
  * numeric order equals the surface order) and reconcile the RHS:
673
1212
  *
674
- * - RHS is a Face: COMPARABLE self strips it; else it must respond to
675
- * to_comparable (reconcile to self's reference, e.g. unit alignment,
676
- * which may raise on loss); else raise.
677
- * - RHS is plain (bare number / non-Face array / Scalar): a direct storage
678
- * compare, which only a COMPARABLE self licenses -- a non-COMPARABLE
679
- * Face (e.g. a unit-bearing CATime) rejects it.
1213
+ * the RHS goes through ca_face_reconcile_operand: a COMPARABLE self takes
1214
+ * a plain RHS or a COMPARABLE Face, a self with to_comparable brings any
1215
+ * RHS into its space, and anything else is refused.
680
1216
  *
681
- * A Face over FIXLEN storage (CARecord / string faces) is left untouched:
1217
+ * A Face over FIXLEN storage (CARecord / string faces) keeps its Face:
682
1218
  * memcmp on its bytes is already the correct order/equality, same as a plain
683
- * fixlen array. Both operands are rebound to storage via *pself / *pother.
1219
+ * fixlen array; only the RHS is reconciled. Both operands are rebound via
1220
+ * *pself / *pother.
684
1221
  * No-op when self is not a Face. */
685
1222
  void
686
1223
  ca_face_reconcile_comparison (volatile VALUE *pself, volatile VALUE *pother)
@@ -698,54 +1235,30 @@ ca_face_reconcile_comparison (volatile VALUE *pself, volatile VALUE *pother)
698
1235
 
699
1236
  storage = ca_strip_face(sc);
700
1237
  if ( storage->data_type == CA_FIXLEN ) {
701
- /* genuine fixlen semantics: memcmp is the correct comparison, leave
702
- the Face(s) in place (same result as a plain fixlen array). */
1238
+ /* genuine fixlen semantics: memcmp is the correct comparison, so self
1239
+ keeps its Face (same result as a plain fixlen array) and only the
1240
+ operand is brought to bytes of the same meaning. */
1241
+ *pother = ca_face_reconcile_operand(self, other, "comparison");
703
1242
  return;
704
1243
  }
705
1244
 
706
- if ( ! ca_test_flag(sc, CA_FLAG_FACE_ORDERABLE_STORAGE) ) {
707
- rb_raise(rb_eArgError,
708
- "comparison: Face operand (%s) is not orderable by storage; "
709
- "use ca.parent to descend to storage",
710
- rb_obj_classname(self));
711
- }
1245
+ ca_face_require_orderable(self, "comparison", "compare");
712
1246
 
713
1247
  {
714
- VALUE self_ref = self; /* reference before stripping (for to_comparable) */
715
- int comparable = ca_test_flag(sc, CA_FLAG_FACE_COMPARABLE_STORAGE);
716
- int other_is_face = 0;
717
-
718
- if ( rb_obj_is_kind_of(other, rb_cCArray) ) {
719
- CArray *oc;
720
- TypedData_Get_Struct(other, CArray, &carray_data_type, oc);
721
- other_is_face = ca_is_face(oc);
722
- }
723
-
724
- if ( comparable ) {
725
- /* directly comparable Face reference: strip a Face operand, take a
726
- plain operand as-is (storage compare is licensed). */
727
- if ( other_is_face ) {
728
- other = rb_ca_strip_face_value(other);
729
- }
730
- }
731
- else if ( rb_respond_to(self_ref, rb_intern("to_comparable")) ) {
732
- /* non-COMPARABLE Face reference: the reference brings the operand into
733
- its own space (unit alignment / instant lift), whatever the operand
734
- type -- Face CArray, our Scalar, a Ruby Time / DateTime, etc. The
735
- reference's to_comparable raises if it cannot reconcile it. */
736
- other = rb_funcall(self_ref, rb_intern("to_comparable"), 1, other);
737
- other = rb_ca_strip_face_value(other);
1248
+ /* A Ruby Array is a list of values (to_comparable reads it cell by
1249
+ cell), not one value, so it keeps the length it was given. */
1250
+ int other_was_scalar = ! RTEST(rb_obj_is_carray(other))
1251
+ && ! RB_TYPE_P(other, T_ARRAY);
1252
+ volatile VALUE ref = self, op = other;
1253
+ ca_face_reconcile_pair(&ref, &op, "comparison");
1254
+ /* The reconcile lifts a scalar operand to a length-1 array; unwrap it
1255
+ so the comparison stays array-vs-scalar instead of a shape check
1256
+ against [1] (which only a 1-D reference would pass). */
1257
+ if ( other_was_scalar && RTEST(rb_obj_is_carray(op)) ) {
1258
+ op = rb_ca_fetch_addr(op, 0);
738
1259
  }
739
- else {
740
- rb_raise(rb_eArgError,
741
- "comparison: non-comparable Face reference (%s) has no "
742
- "to_comparable to reconcile the operand; use ca.parent to "
743
- "compare the hidden storage explicitly",
744
- rb_obj_classname(self_ref));
745
- }
746
-
747
- *pself = rb_ca_strip_face_value(self);
748
- *pother = other;
1260
+ *pself = rb_ca_strip_face_value(ref);
1261
+ *pother = op;
749
1262
  }
750
1263
  }
751
1264
 
@@ -771,4 +1284,14 @@ Init_ca_face (void)
771
1284
  rb_define_singleton_method(rb_cCArray, "face_state_portable?",
772
1285
  rb_ca_s_face_state_portable_p, 0);
773
1286
  rb_define_method(rb_cCArray, "face_lift", rb_ca_face_lift_method, 1);
1287
+ rb_define_private_method(rb_cCArray, "orderable_storage?",
1288
+ rb_ca_orderable_storage_p, 0);
1289
+ rb_define_private_method(rb_cCArray, "comparable_storage?",
1290
+ rb_ca_comparable_storage_p, 0);
1291
+ rb_define_private_method(rb_cCArray, "face_storage",
1292
+ rb_ca_face_storage, 0);
1293
+ rb_define_private_method(rb_cCArray, "reconcile_operand",
1294
+ rb_ca_reconcile_operand, 2);
1295
+ rb_define_private_method(rb_cCArray, "face_storage_method",
1296
+ rb_ca_face_storage_method, 0);
774
1297
  }