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/lib/carray/string.rb CHANGED
@@ -17,20 +17,28 @@ class CArray
17
17
  # CArray.string([a, nil, b]) # nil → masked element
18
18
  # CArray.string(other_ca) # from a String Face / object / raw fixlen
19
19
  #
20
- # `nil` entries become masked cells; "" (empty) is a valid distinct value.
21
- # A CArray source is normalised through a String Face ({CArray.string_face_of}):
22
- # a String Face converts, CA_OBJECT storage wraps, a raw CA_FIXLEN reads as
23
- # NUL-stripped strings; a numeric / boolean array is rejected (stringify with
24
- # {#format} / {CArray.format}).
20
+ # A cell of a CAString is a String. In the Array and block forms `nil`
21
+ # becomes a masked cell; "" (empty) is a valid distinct value. Any other
22
+ # value (a Symbol, a number, a nested Array) is refused: the values are one
23
+ # cell each, so a CAString of more than one dimension is built from an
24
+ # object array (`CArray.string(CA_OBJECT(nested))`).
25
+ # A CArray source is normalised through a String Face: a String Face
26
+ # converts, CA_OBJECT storage wraps zero-copy (every cell that is not
27
+ # masked must be a String, `nil` included: mask it instead), a raw
28
+ # CA_FIXLEN reads as NUL-stripped strings; a numeric / boolean array is
29
+ # rejected (stringify with {#format} / {CArray.format}).
25
30
  # @overload string(values)
26
31
  # Returns a {CAString} wrapping a CA_OBJECT entity of the given values.
27
32
  # @param values [Array<String, nil>] source values.
28
33
  # @return [CAString]
34
+ # @raise [TypeError] if a value is neither a String nor `nil`.
29
35
  # @overload string(ca)
30
36
  # Returns a {CAString} of the string-bearing CArray `ca`.
31
37
  # @param ca [CArray] a String Face, CA_OBJECT, or raw CA_FIXLEN array.
32
38
  # @return [CAString]
33
39
  # @raise [CArray::DataTypeError] if `ca` is numeric / boolean.
40
+ # @raise [TypeError] if a cell of an object array that is not masked is
41
+ # not a String.
34
42
  # @overload string(n) { |i| ... }
35
43
  # Returns an `n`-element {CAString} filled by the block, following the
36
44
  # arity-0 broadcast convention.
@@ -55,6 +63,12 @@ class CArray
55
63
 
56
64
  entity = CArray.object(values.size)
57
65
  values.each_with_index do |s, i|
66
+ if s.is_a?(Array)
67
+ raise TypeError,
68
+ "CArray.string: value #{i} is an Array; a CAString cell is a " \
69
+ "String (for a CAString of another shape, give " \
70
+ "CArray.string an object array: CArray.string(CA_OBJECT(values)))"
71
+ end
58
72
  entity[i] = s.nil? ? UNDEF : s
59
73
  end
60
74
  CAString.wrap(entity)
@@ -66,4 +80,90 @@ end
66
80
  class CAString
67
81
  include CArray::StringOperationMixin
68
82
  include CArray::StringOperationMixin::Mutable
83
+
84
+ # The String operators and the picks by order run on the object storage,
85
+ # cell by cell, as on any object array. A String's +, * and % give a
86
+ # String (or raise), and a pick gives one of the operands, so the result
87
+ # is text and comes back as a CAString, as the bang forms (add!, mul!)
88
+ # keep it. A lazy operand is computed here.
89
+
90
+ # Per-cell `String#+`.
91
+ # @return [CAString] shape and mask carried.
92
+ def + (other) ; as_text(super) ; end
93
+ # Per-cell `String#+` (the word form of {#+}).
94
+ # @return [CAString]
95
+ def add (other) ; as_text(super) ; end
96
+ # Per-cell `String#*`.
97
+ # @return [CAString] shape and mask carried.
98
+ def * (other) ; as_text(super) ; end
99
+ # Per-cell `String#*` (the word form of {#*}).
100
+ # @return [CAString]
101
+ def mul (other) ; as_text(super) ; end
102
+ # Per-cell `String#%` (format).
103
+ # @return [CAString] shape and mask carried.
104
+ def % (other) ; as_text(super) ; end
105
+ # Per-cell `String#%` (the word form of {#%}).
106
+ # @return [CAString]
107
+ def mod (other) ; as_text(super) ; end
108
+ # The later String of each cell and `other`, cell by cell.
109
+ # @return [CAString]
110
+ def pmax (other) ; as_text(super) ; end
111
+ # The earlier String of each cell and `other`, cell by cell.
112
+ # @return [CAString]
113
+ def pmin (other) ; as_text(super) ; end
114
+ # Another name of {#pmax} (a String has no NaN to let win).
115
+ # @return [CAString]
116
+ def maximum (other) ; as_text(super) ; end
117
+ # Another name of {#pmin}.
118
+ # @return [CAString]
119
+ def minimum (other) ; as_text(super) ; end
120
+ # Another name of {#pmax}.
121
+ # @return [CAString]
122
+ def fmax (other) ; as_text(super) ; end
123
+ # Another name of {#pmin}.
124
+ # @return [CAString]
125
+ def fmin (other) ; as_text(super) ; end
126
+
127
+ # The String a blank cell holds: the cell a fresh {#template} gives and
128
+ # the one {#shift} fills by default. Frozen, because every blank cell
129
+ # shares it.
130
+ BLANK = "".freeze
131
+
132
+ # A same-shape CAString. With no block every cell is {BLANK}; a block
133
+ # fills the cells as for any array, and its values must be Strings or
134
+ # UNDEF. Given a data type, the result is a plain array of that type.
135
+ # @overload template(type = nil, **opts)
136
+ # @return [CAString, CArray]
137
+ def template (*args, **opts, &block)
138
+ return super if block || ! args.empty? || ! opts.empty?
139
+ out = super
140
+ out[] = BLANK
141
+ out
142
+ end
143
+
144
+ # Shifts the cells as {CArray#shift} does. The cells shifted in hold
145
+ # {BLANK} unless `fill_value:` names a String or UNDEF.
146
+ # @return [CAString] a view over the shifted cells.
147
+ def shift (*shifts, fill_value: BLANK, **opts)
148
+ super(*shifts, fill_value: fill_value, **opts)
149
+ end
150
+
151
+ # A window as {CArray#window} gives. The cells outside the array hold
152
+ # {BLANK} unless `fill_value:` names a String or UNDEF.
153
+ # @return [CAString] a view over the window.
154
+ def window (*ranges, fill_value: BLANK, **opts)
155
+ super(*ranges, fill_value: fill_value, **opts)
156
+ end
157
+
158
+ # A resized copy as {CArray#resize} gives. The new cells hold {BLANK}
159
+ # unless `fill_value:` names a String or UNDEF.
160
+ # @return [CAString]
161
+ def resize (*newdim, fill_value: BLANK)
162
+ super(*newdim, fill_value: fill_value)
163
+ end
164
+
165
+ private def as_text (result)
166
+ result = result.copy if result.lazy_view?
167
+ CAString.wrap(result)
168
+ end
69
169
  end
@@ -153,13 +153,14 @@ class CArray
153
153
 
154
154
  # @overload extract(regexp, replace = '\0')
155
155
  # Returns a CArray whose cells are each cell's first match of `regexp`
156
- # transformed by `String#sub(regexp, replace)`; non-matching cells
157
- # become `""`.
156
+ # transformed by `String#sub(regexp, replace)`. A cell that does not
157
+ # match is masked (`UNDEF`), so it stays apart from a match of the empty
158
+ # string, and a later `cast` to a number does not read it as 0.
158
159
  # @param regexp [Regexp]
159
160
  # @param replace [String]
160
161
  # @return [CAString]
161
162
  def extract (regexp, replace = '\0')
162
- string_map { |s| regexp.match(s) { |m| m[0].sub(regexp, replace) } || "" }
163
+ string_map { |s| regexp.match(s) { |m| m[0].sub(regexp, replace) } || UNDEF }
163
164
  end
164
165
 
165
166
  # --- inter-Face conversions (String Faces only) ----------------------
@@ -277,8 +278,15 @@ class CArray
277
278
  # boolean array has no string reading and is rejected -- stringify explicitly
278
279
  # with #format / CArray.format. (This mirrors the numeric gate that rejects
279
280
  # numeric reductions on a String Face.)
280
- def self.string_face_of (ca)
281
+ def self.string_face_of (ca, verb = "CArray.string")
281
282
  return ca if ca.is_a?(CArray::StringOperationMixin)
283
+ # Another Face's storage is not its text: a CATime's fixlen surface
284
+ # holds ticks, a categorical's codes.
285
+ if ca.face?
286
+ raise TypeError,
287
+ "#{verb}: a #{ca.class} is not text " \
288
+ "(convert its values to Strings first)"
289
+ end
282
290
  case ca.data_type
283
291
  when :object then CAString.wrap(ca)
284
292
  when :fixlen then CAFixlenString.wrap(ca)
data/lib/carray/struct.rb CHANGED
@@ -217,6 +217,14 @@ class CAStruct
217
217
  return self::MEMBERS
218
218
  end
219
219
 
220
+ # @overload order_by
221
+ # Returns the members the records are ordered by (`order_by:` of
222
+ # {CArray.struct}), or nil when the struct declares no order.
223
+ # @return [Array<String>, nil]
224
+ def order_by
225
+ const_defined?(:ORDER_BY) ? self::ORDER_BY : nil
226
+ end
227
+
220
228
  # @overload fields
221
229
  # Returns the struct's members as {CAStruct::Field} objects
222
230
  # in declaration order. Cached on the class.
@@ -318,64 +326,83 @@ class CAStruct
318
326
  end
319
327
  end
320
328
 
321
- protected
322
-
323
- def __data__
324
- @data
329
+ # A copy owns its bytes (dup and clone of the struct object would
330
+ # otherwise share the one-record array behind it).
331
+ def initialize_copy (other)
332
+ super
333
+ @data = @data.copy
325
334
  end
326
335
 
327
- public
336
+ # Freezing an element also stops writes to its bytes.
337
+ # @return [self]
338
+ def freeze
339
+ @data.freeze
340
+ super
341
+ end
328
342
 
329
- # Member access (`record[:name]` / `record["name"]` / `record[i]`).
330
- # Step 3 (3.0): the per-type `case` was replaced by a class-level
331
- # DISPATCH_TABLE built once at struct-definition time. Each known
332
- # member has a frozen `[reader_proc, writer_proc]` pair that closes
333
- # over its offset / type / opts, so the per-call work is one Hash
334
- # lookup + one Proc#call. Unknown names fall back to `send(name)`
335
- # so subclasses can define computed members via plain Ruby methods.
343
+ # Member access. A member is named by a String (`s["lat"]`) or its
344
+ # Integer position (`s[0]`); its accessor (`s.lat`) reads it too. A
345
+ # Symbol is not a member name here, and a name that is not a member
346
+ # raises. These Ruby bodies state the rule; the methods that run are
347
+ # the C ones installed at the bottom of this file.
336
348
  # @overload [](name)
337
- # Returns the value of member `name` (Integer indexes the
338
- # member list). Unknown names fall through to `send(name)`.
339
- # @param name [Symbol, String, Integer]
349
+ # Returns the value of member `name`.
350
+ # @param name [String, Integer]
340
351
  # @return [Object]
352
+ # @raise [TypeError] for a Symbol or another type of name.
353
+ # @raise [ArgumentError] when `name` is not a member.
341
354
  def [] (name)
342
- if name.kind_of?(Integer)
343
- name = members[name]
344
- end
345
- pair = self.class::DISPATCH_TABLE[name.to_s]
346
- if pair
347
- pair[0].call(@data)
348
- else
349
- send(name)
350
- end
355
+ pair = self.class::DISPATCH_TABLE[member_name(name)]
356
+ pair[0].call(@data)
351
357
  end
352
358
 
353
359
  # @overload []=(name, val)
354
- # Sets member `name` to `val`. Unknown names fall through to
355
- # `send("#{name}=", val)`.
356
- # @param name [Symbol, String, Integer]
360
+ # Sets member `name` to `val`.
361
+ # @param name [String, Integer]
357
362
  # @param val [Object]
358
363
  # @return [Object] `val`.
364
+ # @raise [TypeError] for a Symbol or another type of name.
365
+ # @raise [ArgumentError] when `name` is not a member.
359
366
  def []= (name, val)
360
- if name.kind_of?(Integer)
361
- name = members[name]
362
- end
363
- pair = self.class::DISPATCH_TABLE[name.to_s]
364
- if pair
365
- pair[1].call(@data, val)
367
+ pair = self.class::DISPATCH_TABLE[member_name(name)]
368
+ pair[1].call(@data, val)
369
+ end
370
+
371
+ private def member_name (name)
372
+ case name
373
+ when Symbol
374
+ if self.class::MEMBER_TABLE.key?(name.to_s)
375
+ raise TypeError, "a struct member is named by a String (s[\"#{name}\"]) " \
376
+ "or read by its accessor (s.#{name}), not by a Symbol"
377
+ end
378
+ raise TypeError, "a struct member is named by a String, not by a Symbol " \
379
+ "(#{self.class.inspect} has no member #{name.to_s.inspect} either)"
380
+ when Integer
381
+ key = members[name]
382
+ when String
383
+ key = name
366
384
  else
367
- send(name.to_s + "=", val)
385
+ raise TypeError, "a struct member is named by a String or an Integer " \
386
+ "position (got #{name.class})"
368
387
  end
388
+ unless key && self.class::MEMBER_TABLE.key?(key)
389
+ raise ArgumentError, "#{self.class.inspect} has no member #{name.inspect} " \
390
+ "(members: #{members.inspect})"
391
+ end
392
+ key
369
393
  end
370
394
 
371
395
  # @overload each { |value| ... }
372
- # Yields each member value in declaration order.
396
+ # Yields each member value in declaration order (an Enumerator
397
+ # without a block).
373
398
  # @yieldparam value [Object]
374
399
  # @return [self]
375
400
  def each
401
+ return to_enum(:each) { length } unless block_given?
376
402
  members.each do |name|
377
403
  yield(self[name])
378
404
  end
405
+ self
379
406
  end
380
407
 
381
408
  # @overload each_pair { |name, value| ... }
@@ -385,9 +412,19 @@ class CAStruct
385
412
  # @yieldparam value [Object]
386
413
  # @return [self]
387
414
  def each_pair
415
+ return to_enum(:each_pair) { length } unless block_given?
388
416
  members.each do |name|
389
417
  yield(name.intern, self[name])
390
418
  end
419
+ self
420
+ end
421
+
422
+ # @overload to_h
423
+ # Returns the members as a Hash of member name (Symbol) to value, in
424
+ # declaration order.
425
+ # @return [Hash{Symbol => Object}]
426
+ def to_h
427
+ each_pair.to_h
391
428
  end
392
429
 
393
430
  # @overload length
@@ -432,16 +469,54 @@ class CAStruct
432
469
  return ["<", self.class.inspect, " ", table.inspect[1..-2], ">"].join
433
470
  end
434
471
 
435
- # Value comparison against another record of the same class. Unlike
436
- # `eql?`, which compares the raw bytes, this compares the decoded members.
472
+ alias to_s inspect
473
+
474
+ # Equality with another record of the same class, by the record's bytes
475
+ # (the same as {#eql?}): a struct is its bytes, so a NaN member equals
476
+ # itself, 0.0 and -0.0 differ, and so do records whose padding bytes
477
+ # differ. A record array's `eq` compares records the same way.
437
478
  # @return [Boolean]
438
479
  def == (other)
439
- case other
440
- when self.class
441
- return @data == other.__data__
442
- else
443
- return false
480
+ other.is_a?(self.class) && encode == other.encode
481
+ end
482
+
483
+ # Order against a record of the same class, by the members the struct
484
+ # was declared with in `order_by:`, compared in that order. NaN sorts
485
+ # after every number. nil when the other is not a record of this class,
486
+ # or when the struct declares no order.
487
+ # @return [Integer, nil]
488
+ def <=> (other)
489
+ keys = self.class.order_by
490
+ return nil unless keys && other.class.equal?(self.class)
491
+ keys.each do |key|
492
+ c = CAStruct.compare_key(self[key], other[key])
493
+ return c unless c == 0
444
494
  end
495
+ 0
496
+ end
497
+
498
+ # @!method <(other)
499
+ # @raise [ArgumentError] when the struct declares no `order_by:`, or
500
+ # `other` is not a record of the same class.
501
+ # @!method <=(other)
502
+ # @!method >(other)
503
+ # @!method >=(other)
504
+ [:<, :<=, :>, :>=].each do |op|
505
+ define_method(op, &Ractor.make_shareable(proc do |other|
506
+ c = (self <=> other)
507
+ if c.nil?
508
+ raise ArgumentError,
509
+ "comparison of #{self.class.inspect} with #{other.class.inspect} failed" +
510
+ (self.class.order_by ? "" : " (the struct declares no order_by:)")
511
+ end
512
+ c.public_send(op, 0)
513
+ end))
514
+ end
515
+
516
+ # One member of {#<=>}: the order a sort puts the member's values in
517
+ # (NaN after every number).
518
+ def self.compare_key (a, b)
519
+ CArray.send(:object_order_cmp, a, b)
445
520
  end
446
521
 
447
522
  # Byte-level identity. Two CAStruct instances are eql? iff they are
@@ -465,6 +540,7 @@ class CAStruct
465
540
  # @return [self]
466
541
  # @raise [CAStruct::DecodeError] on an unsupported input.
467
542
  def decode (data)
543
+ raise FrozenError.new("can't modify frozen #{self.class}", receiver: self) if frozen?
468
544
  case data
469
545
  when String
470
546
  @data.load_binary(data)
@@ -486,25 +562,18 @@ class CAStruct
486
562
  end
487
563
 
488
564
  # @overload swap_bytes!
489
- # Byte-swaps `self` in place (endian flip).
565
+ # Byte-swaps each member of `self` in place (endian flip).
490
566
  # @return [self]
491
567
  def swap_bytes!
492
- @data[] = @data.swap_bytes
493
- return self
568
+ decode(swap_bytes.encode)
494
569
  end
495
570
 
496
571
  # @overload swap_bytes
497
- # Returns a fresh byte-swapped copy of `self`.
572
+ # Returns a fresh copy of `self` with each member byte-swapped, as
573
+ # a record array's swap_bytes swaps its records.
498
574
  # @return [CAStruct]
499
575
  def swap_bytes
500
- return self.class.decode(@data.swap_bytes.dump_binary)
501
- end
502
-
503
- # @overload to_ptr
504
- # Returns a `Fiddle::Pointer` to the backing storage.
505
- # @return [Fiddle::Pointer]
506
- def to_ptr
507
- return @data.to_ptr
576
+ return CARecord.wrap(@data, self.class).swap_bytes[0]
508
577
  end
509
578
 
510
579
  end
@@ -527,9 +596,19 @@ class CArray
527
596
  # Returns a new {CAStruct} subclass defined by the block via
528
597
  # {CAStruct::Builder}. Options control alignment, packing, and
529
598
  # endianness.
530
- # @param opt [Hash]
599
+ #
600
+ # `order_by:` names the members records are ordered by, compared in
601
+ # that order (`order_by: [:lat, :id]` sorts by `lat`, then `id` among
602
+ # equal `lat`). It gives the records `<=>` and the comparison
603
+ # operators, and gives a {CARecord} of them `sort`, `min`, `max`,
604
+ # `partition_copy` and their index forms. A struct without it has no
605
+ # order, and those raise. NaN sorts after every number, as in a
606
+ # numeric array.
607
+ # @param opt [Hash] `pack:`, `size:`, `order_by:`.
531
608
  # @yield DSL calls in {CAStruct::Builder}.
532
609
  # @return [Class] anonymous {CAStruct} subclass.
610
+ # @raise [CAStruct::DefinitionError] when `order_by:` names something
611
+ # that is not a member, or a nested record.
533
612
  def self.struct (opt={}, &block)
534
613
  return CAStruct::Builder.new(:struct, opt).define(&block)
535
614
  end
@@ -637,8 +716,8 @@ end
637
716
  # in Builder sets them up before any instance is created). Subclasses
638
717
  # created by Builder later don't need re-installation -- they inherit the
639
718
  # C methods from CAStruct.
640
- if CArray.respond_to?(:__install_castruct_methods__)
641
- CArray.__install_castruct_methods__(CAStruct)
719
+ if CArray.respond_to?(:install_struct_methods, true)
720
+ CArray.send(:install_struct_methods, CAStruct)
642
721
  end
643
722
 
644
-
723
+ require 'carray/record_order'