carray 3.0.1 → 3.0.3

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
@@ -5,15 +5,14 @@
5
5
  * Established in PROPOSAL_L0_AUTHOR_SURFACE L0.1 (2026-06-11). Extracts
6
6
  * the xfer_all-aware lifecycle template previously duplicated 7-times in
7
7
  * ext/carray_call_cfunc.c (= one body per arity 1..7) into a single shared
8
- * helper. Same helper will back the forthcoming CA_FOR_EACH_ELEMENT macro
9
- * family (L0.2a) and the WHOLE_BUFFER family (L0.2c).
8
+ * helper, also used by the chunked slab family (ca_call_cslab_*).
10
9
  *
11
10
  * Lifecycle template:
12
11
  * (1) per-operand acquire
13
12
  * OUTPUT (fsync=='1') -> ca_attach + use ca->ptr (sync on release)
14
13
  * INPUT (fsync=='0') alias-able -> ca_attach + use ca->ptr
15
14
  * INPUT (fsync=='0') non-alias -> xmalloc + ca_xfer_all(GET)
16
- * (2) broadcast shape check (scalar collapse + n_kernel agreement)
15
+ * (2) operand pairing (scalar collapse; arrays must agree in shape)
17
16
  * (3) mask OR across INPUT operands -> m0 (xmalloc, NULL if none)
18
17
  * (4) mask propagate to OUTPUT operands (overwrite output->mask)
19
18
  * (5) caller runs the per-cell loop using base[] + stride[]
@@ -64,15 +63,10 @@ typedef struct ca_sweep_state {
64
63
  boolean8_t *m0;
65
64
  ca_size_t n_kernel;
66
65
  const char *src_label;
67
- /* no_mask: if non-zero, ca_sweep_acquire raises when any INPUT operand has
68
- * a mask. Used by the NO_MASK macro forms (CA_FOR_EACH_ELEMENT etc.) to
69
- * implement Q3=(ii) runtime error policy. Default 0 (= masked INPUT OK,
70
- * m0 built as usual). Caller sets before calling ca_sweep_acquire. */
71
- int no_mask;
72
66
 
73
67
  /* === chunked path fields (L0.1 chunk, PROPOSAL_L0_AUTHOR_SURFACE) === */
74
68
  /* Used by ca_sweep_acquire_chunked / ca_sweep_next_chunk / ca_sweep_release_chunked
75
- * (= the sweep ELEMENT macro family). Unused by the whole-materialise path
69
+ * (= the chunked slab family, ca_call_cslab_*). Unused by the whole-materialise path
76
70
  * (ca_sweep_acquire / ca_sweep_release) — cfunc and other whole-buffer callers
77
71
  * leave these zero. */
78
72
  char **base_orig; /* per-op original ptr (= ca->ptr for
@@ -84,14 +78,20 @@ typedef struct ca_sweep_state {
84
78
  ca_size_t chunk_n_max; /* max chunk size from ca_chunk_compute_n */
85
79
  ca_size_t inner; /* product of dims except outermost (= row
86
80
  size in elements for outer-axis chunking) */
87
- int chunked_state; /* 0=pre-init, 1=active, 2=done */
81
+ int chunked_state; /* 0=pre-init, 1=active, 2=done,
82
+ 3=aborted (a read raised; resources
83
+ already given back) */
88
84
  boolean8_t *mask_scratch; /* chunk-sized staging for the per-chunk
89
85
  mask OR; NULL when no INPUT is masked */
90
86
  } ca_sweep_state_t;
91
87
 
92
- /* Validate fsync length, acquire per-op buffers, compute broadcast shape +
93
- * strides, build mask m0, propagate mask to OUTPUTs. May raise (fsync
94
- * length mismatch, shape mismatch). */
88
+ /* Validate fsync length, pair the operands (shape), acquire per-op
89
+ * buffers, build mask m0, propagate mask to OUTPUTs. May raise: refusals
90
+ * (fsync length, shape mismatch) before
91
+ * anything is held, and a raising operand read after -- in which case
92
+ * everything acquired so far is detached / freed before the raise
93
+ * propagates, so the caller must not call release. The chunked path
94
+ * does the same for acquire_chunked and each ca_sweep_next_chunk. */
95
95
  void ca_sweep_acquire (ca_sweep_state_t *st);
96
96
 
97
97
  /* Reverse-order release: sync OUTPUTs, detach attached / xfree owned_buf,
@@ -99,13 +99,22 @@ void ca_sweep_acquire (ca_sweep_state_t *st);
99
99
  * state without re-initializing the caller-owned arrays. */
100
100
  void ca_sweep_release (ca_sweep_state_t *st);
101
101
 
102
- /* Strict full-shape equality check used by INOUT macros (AC5: silent-
103
- * corruption seam prevention). Raises if ndim differs or any dim[k]
104
- * differs. Returns void; raises on mismatch. */
105
- void ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out,
106
- const char *src_label);
102
+ /* Run walk(arg) -- the caller's loop over the acquired state -- and then
103
+ * release. If walk raises (a callback refusing a value), everything the
104
+ * engine holds is detached / freed without syncing before the raise
105
+ * propagates. Use in place of calling ca_sweep_release /
106
+ * ca_sweep_release_chunked yourself whenever the loop can raise. The
107
+ * chunked walk calls ca_sweep_next_chunk itself. */
108
+ void ca_sweep_run (ca_sweep_state_t *st, VALUE (*walk)(VALUE), VALUE arg);
109
+ void ca_sweep_run_chunked (ca_sweep_state_t *st, VALUE (*walk)(VALUE), VALUE arg);
107
110
 
108
- /* ===== Chunked path API (sweep ELEMENT macro family, L0.1 chunk) =====
111
+ /* Shape pairing shared with rb_ca_template_n: non-zero when a and b have
112
+ * the same ndim and dims; the refusal raises ArgumentError naming both
113
+ * shapes. */
114
+ int ca_sweep_same_shape (CArray *a, CArray *b);
115
+ NORETURN(void ca_sweep_refuse_shapes (CArray *a, CArray *b));
116
+
117
+ /* ===== Chunked path API (ca_call_cslab_*) =====
109
118
  *
110
119
  * Unlike ca_sweep_acquire / ca_sweep_release (= whole-buffer materialise used
111
120
  * by cfunc), the chunked path keeps INPUT memory peak bounded to chunk
@@ -113,7 +122,7 @@ void ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out,
113
122
  * memory peak guarantee: shrinking views materialise into chunk scratch
114
123
  * sized to inner-axis multiples, not full operand size.
115
124
  *
116
- * Usage shape (inside a macro):
125
+ * Usage shape:
117
126
  * ca_sweep_acquire_chunked(&st);
118
127
  * while (ca_sweep_next_chunk(&st)) {
119
128
  * for (k = 0; k < st.chunk_n; k++) {
@@ -141,9 +150,8 @@ void ca_sweep_check_same_shape (CArray *ca_in, CArray *ca_out,
141
150
  * indexed within the chunk, NOT by the flat cell index. (This differs
142
151
  * from the whole-buffer path, where m0 spans n_kernel.)
143
152
  *
144
- * An author may WRITE m0[k] during the chunk loop (= the INOUT_MASKED
145
- * forms' m_out). Those writes are flushed into the OUTPUT operands'
146
- * masks when the chunk finishes -- at the top of the following
153
+ * The chunk's m0 is flushed into the OUTPUT operands' masks when the
154
+ * chunk finishes -- at the top of the following
147
155
  * ca_sweep_next_chunk, and once more in ca_sweep_release_chunked for
148
156
  * the last chunk -- so the OUTPUT masks are created up front in
149
157
  * acquire_chunked rather than propagated in one go at release.
@@ -27,6 +27,7 @@ ca_triop_kernel_lookup_vvv (uint16_t op_id, int8_t common_dt)
27
27
  case CA_TRIOP_FMA: return ca_triop_fma [common_dt];
28
28
  case CA_TRIOP_FMS: return ca_triop_fms [common_dt];
29
29
  case CA_TRIOP_CLIP: return ca_triop_clip [common_dt];
30
+ case CA_TRIOP_SELECT: return ca_triop_select [common_dt];
30
31
  default: return NULL;
31
32
  }
32
33
  }
@@ -38,7 +39,9 @@ ca_triop_kernel_lookup_vvv (uint16_t op_id, int8_t common_dt)
38
39
  int8_t
39
40
  ca_lazy_promote_triop (uint16_t op_id, int8_t dt1, int8_t dt2, int8_t dt3)
40
41
  {
41
- (void) op_id; /* uniform 3-way promote across all currently-defined triops */
42
+ if ( op_id == CA_TRIOP_SELECT ) {
43
+ return (int8_t) ca_promote_type(dt2, dt3);
44
+ }
42
45
  return (int8_t) ca_promote_type(ca_promote_type(dt1, dt2), dt3);
43
46
  }
44
47
 
@@ -11,7 +11,7 @@
11
11
  runs (cast-before invariant, mirrors CABinOp). Output data_type is
12
12
  always the common data_type (preserve).
13
13
 
14
- Scope: fma / fms / clip (see ext/mkkernel.rb :fma / :fms / :clip).
14
+ Scope: fma / fms / clip / select (see ext/mkkernel.rb).
15
15
  None of the currently-defined triops trap on integer zero divisor.
16
16
 
17
17
  ---------------------------------------------------------------------------- */
@@ -29,6 +29,7 @@ enum {
29
29
  CA_TRIOP_FMA = 0, /* a * b + c (single-rounding on float) */
30
30
  CA_TRIOP_FMS = 1, /* a * b - c (single-rounding on float) */
31
31
  CA_TRIOP_CLIP = 2, /* min(max(a, lo), hi), NaN-preserving */
32
+ CA_TRIOP_SELECT = 3, /* a ? b : c (lazy then_else) */
32
33
 
33
34
  CA_TRIOP_COUNT
34
35
  };
@@ -40,6 +41,7 @@ enum {
40
41
  extern ca_triop_func_t ca_triop_fma [CA_NTYPE];
41
42
  extern ca_triop_func_t ca_triop_fms [CA_NTYPE];
42
43
  extern ca_triop_func_t ca_triop_clip [CA_NTYPE];
44
+ extern ca_triop_func_t ca_triop_select [CA_NTYPE];
43
45
 
44
46
  /* ------------------------------------------------------------------- */
45
47
  /* Dispatch API */
@@ -49,7 +51,8 @@ extern ca_triop_func_t ca_triop_clip [CA_NTYPE];
49
51
  not implemented at the requested data_type. */
50
52
  ca_triop_func_t ca_triop_kernel_lookup_vvv (uint16_t op_id, int8_t common_dt);
51
53
 
52
- /* 3-way promotion: common data_type of (dt1, dt2, dt3). */
54
+ /* 3-way promotion: common data_type of (dt1, dt2, dt3). For select it is
55
+ that of the two branches (dt2, dt3); the condition is converted to it. */
53
56
  int8_t ca_lazy_promote_triop (uint16_t op_id, int8_t dt1, int8_t dt2, int8_t dt3);
54
57
 
55
58
  /* Each operand's cast-target data_type (= common data_type for all three,
@@ -0,0 +1,463 @@
1
+ /* CSV body reader for CAFrame.from_csv.
2
+
3
+ CArray.__csv_read_body_as_string_cells__(text, sep, quote, ncol, keep)
4
+ -> [ncol, cells, records]
5
+ reads every record of text into one flat Array of nrow * ncol cells, row
6
+ after row: a String for a field, UNDEF for a missing one (an unquoted
7
+ empty field, or a cell a short row never reached). keep, when not nil,
8
+ is the ascending Array of the indexes of the columns to read: a row then
9
+ has a cell for each of them only, and the fields of the others are passed
10
+ over without being copied out of the text. records counts the
11
+ records read, blank lines included, as the Ruby tokenizer numbers them.
12
+ With ncol 0 the column count is the first record's, as for a file with no
13
+ header; a later record that is longer is then one this reader declines.
14
+
15
+ CArray.__csv_read_body_as_const_string_columns__(text, sep, quote, ncol, keep)
16
+ -> [ncol, buffer, [pairs, ...], records]
17
+ reads the same records into one fixlen-16 (start, end) pair entity per
18
+ column read, the layout CAConstString.wrap takes, with missing cells masked.
19
+ The pairs index buffer, which is text itself when no field held a doubled
20
+ quote, and text followed by those fields' unescaped bytes otherwise, so
21
+ the cells are not copied out of the text.
22
+
23
+ Both follow the rules of the Ruby tokenizer in
24
+ lib/carray/frame/csv_parser.rb, which stays the reference: a quoted field
25
+ may hold the separator, doubled quotes and line breaks; a line that is
26
+ empty, or only spaces and tabs with no separator, is not a row unless the
27
+ file has one column. They answer nil, for the Ruby tokenizer to read the
28
+ text and say what is wrong, when the text is not UTF-8 or US-ASCII or has
29
+ an invalid byte, when a record is malformed or longer than ncol, and when
30
+ keep names no column or one past the last. */
31
+
32
+ #include "carray.h"
33
+ #include "carray_internal.h"
34
+ #include "ruby/encoding.h"
35
+
36
+ static int
37
+ ca_csv_at (const char *s, long n, long p, const char *t, long tlen)
38
+ {
39
+ return p + tlen <= n && memcmp(s + p, t, tlen) == 0;
40
+ }
41
+
42
+ /* The end of the line content that starts at p: the position of its "\n",
43
+ or n; *next is where the following line starts. A "\r" before the "\n",
44
+ or at the end of the text, is not content. */
45
+ static long
46
+ ca_csv_line_end (const char *s, long n, long p, long *next)
47
+ {
48
+ const char *nl = memchr(s + p, '\n', n - p);
49
+ long e = nl ? nl - s : n;
50
+ *next = nl ? e + 1 : n;
51
+ if ( e > p && s[e - 1] == '\r' ) {
52
+ e -= 1;
53
+ }
54
+ return e;
55
+ }
56
+
57
+ /* A growing list of (start, end) pairs. A pair at or past the length of
58
+ the text indexes the unescaped tail; a missing cell is (-1, -1). */
59
+ typedef struct {
60
+ int64_t *v;
61
+ long len, cap; /* in pairs */
62
+ } ca_csv_cell_ranges;
63
+
64
+ static void
65
+ ca_csv_push_cell_range (ca_csv_cell_ranges *a, int64_t start, int64_t end)
66
+ {
67
+ if ( a->len == a->cap ) {
68
+ a->cap = a->cap ? 2 * a->cap : 4096;
69
+ REALLOC_N(a->v, int64_t, 2 * a->cap);
70
+ }
71
+ a->v[2 * a->len] = start;
72
+ a->v[2 * a->len + 1] = end;
73
+ a->len++;
74
+ }
75
+
76
+ typedef struct {
77
+ ca_csv_cell_ranges cells; /* row after row */
78
+ ca_csv_cell_ranges pending; /* blank lines before the first record */
79
+ VALUE tail; /* unescaped quoted fields, or Qnil */
80
+ VALUE keep; /* the indexes of the columns to read, or Qnil */
81
+ char *kept; /* keep as a byte per column, 1 for one read;
82
+ NULL for every column, or before ncol is known */
83
+ long ncol;
84
+ long width; /* the cells of a row: those of the columns read */
85
+ long records;
86
+ } ca_csv_walk_t;
87
+
88
+ /* Set w->kept and w->width from w->keep for w->ncol columns. Returns 0 when
89
+ keep names no column or one past the last. */
90
+ static int
91
+ ca_csv_set_kept_columns (ca_csv_walk_t *w)
92
+ {
93
+ long i;
94
+ w->kept = ALLOC_N(char, w->ncol);
95
+ memset(w->kept, 0, w->ncol);
96
+ for (i = 0; i < RARRAY_LEN(w->keep); i++) {
97
+ long j = NUM2LONG(rb_ary_entry(w->keep, i));
98
+ if ( j < 0 || j >= w->ncol ) {
99
+ return 0;
100
+ }
101
+ if ( ! w->kept[j] ) {
102
+ w->kept[j] = 1;
103
+ w->width++;
104
+ }
105
+ }
106
+ return w->width > 0;
107
+ }
108
+
109
+ /* Read every record of s into w. Returns 0 when the text is one this
110
+ reader declines (see the header comment). The cell ranges it grows are
111
+ the caller's to free, whether it returns or raises. */
112
+ static int
113
+ ca_csv_walk_records (const char *s, long n, const char *sp, long slen,
114
+ const char *qp, long qlen, ca_csv_walk_t *w)
115
+ {
116
+ long ncol = w->ncol, p = 0;
117
+
118
+ if ( ! NIL_P(w->keep) && ncol > 0 && ! ca_csv_set_kept_columns(w) ) {
119
+ return 0;
120
+ }
121
+ while ( p < n ) {
122
+ long next, e = ca_csv_line_end(s, n, p, &next), k, fields = 0;
123
+ int blank = 1;
124
+
125
+ w->records += 1;
126
+
127
+ /* A blank line: empty, or only spaces and tabs with no separator. */
128
+ for (k = p; k < e; k++) {
129
+ if ( s[k] != ' ' && s[k] != '\t' ) {
130
+ blank = 0;
131
+ break;
132
+ }
133
+ }
134
+ if ( blank ) {
135
+ for (k = p; k + slen <= e; k++) {
136
+ if ( memcmp(s + k, sp, slen) == 0 ) {
137
+ blank = 0;
138
+ break;
139
+ }
140
+ }
141
+ }
142
+ if ( blank ) {
143
+ int64_t a = e > p ? p : -1, b = e > p ? e : -1;
144
+ if ( ncol == 1 ) {
145
+ if ( w->kept == NULL || w->kept[0] ) {
146
+ ca_csv_push_cell_range(&w->cells, a, b);
147
+ }
148
+ }
149
+ else if ( ncol == 0 ) {
150
+ /* Before the first record the column count is not known: keep the
151
+ line, a row if that record turns out to have one column. */
152
+ ca_csv_push_cell_range(&w->pending, a, b);
153
+ }
154
+ p = next;
155
+ continue;
156
+ }
157
+
158
+ for (;;) {
159
+ int64_t a = -1, b = -1;
160
+ /* The field of a column not read is passed over, its text not
161
+ unescaped. */
162
+ int read = w->kept == NULL || ( fields < ncol && w->kept[fields] );
163
+ if ( ca_csv_at(s, n, p, qp, qlen) ) {
164
+ /* A quoted field, which may run onto later lines. */
165
+ long q = p + qlen, start = q, tail0 = -1;
166
+ for (;;) {
167
+ const char *hit = NULL;
168
+ long r = q;
169
+ while ( r + qlen <= n ) {
170
+ const char *c = memchr(s + r, qp[0], n - r);
171
+ if ( c == NULL ) {
172
+ break;
173
+ }
174
+ r = c - s;
175
+ if ( ca_csv_at(s, n, r, qp, qlen) ) {
176
+ hit = c;
177
+ break;
178
+ }
179
+ r++;
180
+ }
181
+ if ( hit == NULL ) {
182
+ return 0; /* unterminated */
183
+ }
184
+ if ( ca_csv_at(s, n, r + qlen, qp, qlen) ) {
185
+ /* a doubled quote is a literal one */
186
+ if ( ! read ) {
187
+ q = r + 2 * qlen;
188
+ continue;
189
+ }
190
+ if ( NIL_P(w->tail) ) {
191
+ w->tail = rb_str_buf_new(256);
192
+ }
193
+ if ( tail0 < 0 ) {
194
+ tail0 = RSTRING_LEN(w->tail);
195
+ rb_str_cat(w->tail, s + start, r + qlen - start);
196
+ }
197
+ else {
198
+ rb_str_cat(w->tail, s + q, r + qlen - q);
199
+ }
200
+ q = r + 2 * qlen;
201
+ continue;
202
+ }
203
+ if ( tail0 < 0 ) {
204
+ a = start;
205
+ b = r;
206
+ }
207
+ else {
208
+ rb_str_cat(w->tail, s + q, r - q);
209
+ a = n + tail0;
210
+ b = n + RSTRING_LEN(w->tail);
211
+ }
212
+ p = r + qlen;
213
+ break;
214
+ }
215
+ e = ca_csv_line_end(s, n, p, &next);
216
+ if ( p != e && ! ca_csv_at(s, n, p, sp, slen) ) {
217
+ return 0; /* text after the closing quote */
218
+ }
219
+ }
220
+ else {
221
+ long r = p;
222
+ if ( slen == 1 && qlen == 1 ) {
223
+ const char sc = sp[0], qc = qp[0];
224
+ while ( r < e && s[r] != sc ) {
225
+ if ( s[r] == qc ) {
226
+ return 0; /* a quote in an unquoted field */
227
+ }
228
+ r++;
229
+ }
230
+ }
231
+ else {
232
+ while ( r < e && ! ca_csv_at(s, n, r, sp, slen) ) {
233
+ if ( ca_csv_at(s, n, r, qp, qlen) ) {
234
+ return 0; /* a quote in an unquoted field */
235
+ }
236
+ r++;
237
+ }
238
+ }
239
+ a = r > p ? p : -1;
240
+ b = r > p ? r : -1;
241
+ p = r;
242
+ }
243
+ if ( ++fields > ncol && ncol > 0 ) {
244
+ return 0; /* a row too long */
245
+ }
246
+ if ( read ) {
247
+ ca_csv_push_cell_range(&w->cells, a, b);
248
+ }
249
+ if ( p < e && ca_csv_at(s, n, p, sp, slen) ) {
250
+ p += slen;
251
+ continue;
252
+ }
253
+ break;
254
+ }
255
+ if ( ncol == 0 ) {
256
+ /* The first record sets the column count; the blank lines before it
257
+ are rows of a one-column file and nothing otherwise. */
258
+ ncol = fields;
259
+ if ( ! NIL_P(w->keep) ) {
260
+ /* Its fields were all read, the columns being unknown until now:
261
+ keep those of the columns read. */
262
+ long j, k = 0;
263
+ w->ncol = ncol;
264
+ if ( ! ca_csv_set_kept_columns(w) ) {
265
+ return 0;
266
+ }
267
+ for (j = 0; j < ncol; j++) {
268
+ if ( w->kept[j] ) {
269
+ w->cells.v[2 * k] = w->cells.v[2 * j];
270
+ w->cells.v[2 * k + 1] = w->cells.v[2 * j + 1];
271
+ k++;
272
+ }
273
+ }
274
+ w->cells.len = k;
275
+ }
276
+ if ( ncol == 1 && w->pending.len > 0 ) {
277
+ long i;
278
+ for (i = 0; i < w->cells.len; i++) {
279
+ ca_csv_push_cell_range(&w->pending, w->cells.v[2 * i], w->cells.v[2 * i + 1]);
280
+ }
281
+ { ca_csv_cell_ranges t = w->cells; w->cells = w->pending; w->pending = t; }
282
+ w->pending.len = 0;
283
+ }
284
+ }
285
+ for (; fields < ncol; fields++) {
286
+ if ( w->kept == NULL || w->kept[fields] ) {
287
+ ca_csv_push_cell_range(&w->cells, -1, -1);
288
+ }
289
+ }
290
+ p = next;
291
+ }
292
+ w->ncol = ncol;
293
+ if ( NIL_P(w->keep) ) {
294
+ w->width = ncol;
295
+ }
296
+ return ncol > 0; /* 0: no record */
297
+ }
298
+
299
+ /* The arguments both readers take, checked; 0 for text they decline. */
300
+ static int
301
+ ca_csv_check_reader_args (VALUE text, VALUE vsep, VALUE vquote, VALUE vncol,
302
+ VALUE keep, long *ncol)
303
+ {
304
+ rb_encoding *enc;
305
+ Check_Type(text, T_STRING);
306
+ Check_Type(vsep, T_STRING);
307
+ Check_Type(vquote, T_STRING);
308
+ if ( ! NIL_P(keep) ) {
309
+ Check_Type(keep, T_ARRAY);
310
+ }
311
+ *ncol = NUM2LONG(vncol);
312
+ enc = rb_enc_get(text);
313
+ if ( ( enc != rb_utf8_encoding() && enc != rb_usascii_encoding() )
314
+ || rb_enc_str_coderange(text) == ENC_CODERANGE_BROKEN || *ncol < 0 ) {
315
+ return 0;
316
+ }
317
+ return RSTRING_LEN(vsep) >= 1 && RSTRING_LEN(vquote) >= 1;
318
+ }
319
+
320
+ /* What a reader makes of the walked records: the Ruby value it answers. */
321
+ typedef VALUE (*ca_csv_build_result) (VALUE text, ca_csv_walk_t *w);
322
+
323
+ typedef struct {
324
+ VALUE text, vsep, vquote, vncol;
325
+ ca_csv_walk_t w;
326
+ ca_csv_build_result build;
327
+ } ca_csv_read_body_context;
328
+
329
+ static VALUE
330
+ ca_csv_read_body_walk_and_build (VALUE varg)
331
+ {
332
+ ca_csv_read_body_context *c = (ca_csv_read_body_context *) varg;
333
+ if ( ! ca_csv_check_reader_args(c->text, c->vsep, c->vquote, c->vncol, c->w.keep,
334
+ &c->w.ncol) ) {
335
+ return Qnil;
336
+ }
337
+ if ( ! ca_csv_walk_records(RSTRING_PTR(c->text), RSTRING_LEN(c->text),
338
+ RSTRING_PTR(c->vsep), RSTRING_LEN(c->vsep),
339
+ RSTRING_PTR(c->vquote), RSTRING_LEN(c->vquote), &c->w) ) {
340
+ return Qnil;
341
+ }
342
+ return c->build(c->text, &c->w);
343
+ }
344
+
345
+ static VALUE
346
+ ca_csv_read_body_free_cell_ranges (VALUE varg)
347
+ {
348
+ ca_csv_read_body_context *c = (ca_csv_read_body_context *) varg;
349
+ xfree(c->w.cells.v);
350
+ xfree(c->w.pending.v);
351
+ xfree(c->w.kept);
352
+ return Qnil;
353
+ }
354
+
355
+ /* Walk the records of text and build the answer from them, with the cell
356
+ ranges freed however that ends: the walk and the build both allocate,
357
+ and so may raise. */
358
+ static VALUE
359
+ ca_csv_read_body (VALUE text, VALUE vsep, VALUE vquote, VALUE vncol, VALUE keep,
360
+ ca_csv_build_result build)
361
+ {
362
+ ca_csv_read_body_context c = {
363
+ text, vsep, vquote, vncol,
364
+ { { NULL, 0, 0 }, { NULL, 0, 0 }, Qnil, keep, NULL, 0, 0, 0 }, build
365
+ };
366
+ VALUE out = rb_ensure(ca_csv_read_body_walk_and_build, (VALUE) &c,
367
+ ca_csv_read_body_free_cell_ranges, (VALUE) &c);
368
+ RB_GC_GUARD(c.w.tail);
369
+ return out;
370
+ }
371
+
372
+ static VALUE
373
+ ca_csv_build_string_cells (VALUE text, ca_csv_walk_t *w)
374
+ {
375
+ volatile VALUE out;
376
+ rb_encoding *enc = rb_enc_get(text);
377
+ const char *s = RSTRING_PTR(text);
378
+ long n = RSTRING_LEN(text), i;
379
+
380
+ out = rb_ary_new_capa(w->cells.len);
381
+ for (i = 0; i < w->cells.len; i++) {
382
+ int64_t a = w->cells.v[2 * i], b = w->cells.v[2 * i + 1];
383
+ VALUE cell;
384
+ if ( a < 0 ) {
385
+ cell = CA_UNDEF;
386
+ }
387
+ else if ( a < n ) {
388
+ cell = rb_enc_str_new(s + a, b - a, enc);
389
+ }
390
+ else {
391
+ cell = rb_enc_str_new(RSTRING_PTR(w->tail) + (a - n), b - a, enc);
392
+ }
393
+ rb_ary_push(out, cell);
394
+ }
395
+ return rb_ary_new3(3, LONG2NUM(w->ncol), out, LONG2NUM(w->records));
396
+ }
397
+
398
+ static VALUE
399
+ ca_csv_build_const_string_columns (VALUE text, ca_csv_walk_t *w)
400
+ {
401
+ volatile VALUE buffer, cols;
402
+ ca_size_t dim[1];
403
+ long nrow, i, j;
404
+
405
+ if ( NIL_P(w->tail) ) {
406
+ buffer = text;
407
+ }
408
+ else {
409
+ buffer = rb_str_buf_new(RSTRING_LEN(text) + RSTRING_LEN(w->tail));
410
+ rb_str_cat(buffer, RSTRING_PTR(text), RSTRING_LEN(text));
411
+ rb_str_cat(buffer, RSTRING_PTR(w->tail), RSTRING_LEN(w->tail));
412
+ rb_enc_copy(buffer, text);
413
+ }
414
+
415
+ nrow = w->cells.len / w->width;
416
+ dim[0] = (ca_size_t) nrow;
417
+ cols = rb_ary_new_capa(w->width);
418
+ for (j = 0; j < w->width; j++) {
419
+ CArray *pe;
420
+ int64_t *range;
421
+ boolean8_t *m = NULL;
422
+ VALUE col = rb_carray_new(CA_FIXLEN, 1, dim, 2 * sizeof(int64_t), NULL);
423
+ rb_ary_push(cols, col);
424
+ TypedData_Get_Struct(col, CArray, &carray_data_type, pe);
425
+ range = (int64_t *) pe->ptr;
426
+ for (i = 0; i < nrow; i++) {
427
+ int64_t a = w->cells.v[2 * (i * w->width + j)];
428
+ int64_t b = w->cells.v[2 * (i * w->width + j) + 1];
429
+ if ( a < 0 ) {
430
+ if ( m == NULL ) {
431
+ ca_create_mask(pe);
432
+ m = (boolean8_t *) pe->mask->ptr;
433
+ }
434
+ m[i] = 1;
435
+ a = b = 0;
436
+ }
437
+ range[2 * i] = a;
438
+ range[2 * i + 1] = b;
439
+ }
440
+ }
441
+ return rb_ary_new3(4, LONG2NUM(w->ncol), buffer, cols, LONG2NUM(w->records));
442
+ }
443
+
444
+ static VALUE
445
+ rb_ca_s_csv_read_body_as_string_cells (VALUE klass, VALUE text, VALUE vsep, VALUE vquote,
446
+ VALUE vncol, VALUE keep)
447
+ {
448
+ return ca_csv_read_body(text, vsep, vquote, vncol, keep, ca_csv_build_string_cells);
449
+ }
450
+
451
+ static VALUE
452
+ rb_ca_s_csv_read_body_as_const_string_columns (VALUE klass, VALUE text, VALUE vsep, VALUE vquote,
453
+ VALUE vncol, VALUE keep)
454
+ {
455
+ return ca_csv_read_body(text, vsep, vquote, vncol, keep, ca_csv_build_const_string_columns);
456
+ }
457
+
458
+ void
459
+ Init_caframe_csv_reader (void)
460
+ {
461
+ rb_define_singleton_method(rb_cCArray, "__csv_read_body_as_string_cells__", rb_ca_s_csv_read_body_as_string_cells, 5);
462
+ rb_define_singleton_method(rb_cCArray, "__csv_read_body_as_const_string_columns__", rb_ca_s_csv_read_body_as_const_string_columns, 5);
463
+ }