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
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,6 +326,20 @@ class CAStruct
318
326
  end
319
327
  end
320
328
 
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
334
+ end
335
+
336
+ # Freezing an element also stops writes to its bytes.
337
+ # @return [self]
338
+ def freeze
339
+ @data.freeze
340
+ super
341
+ end
342
+
321
343
  protected
322
344
 
323
345
  def __data__
@@ -326,56 +348,69 @@ class CAStruct
326
348
 
327
349
  public
328
350
 
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.
351
+ # Member access. A member is named by a String (`s["lat"]`) or its
352
+ # Integer position (`s[0]`); its accessor (`s.lat`) reads it too. A
353
+ # Symbol is not a member name here, and a name that is not a member
354
+ # raises. These Ruby bodies state the rule; the methods that run are
355
+ # the C ones installed at the bottom of this file.
336
356
  # @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]
357
+ # Returns the value of member `name`.
358
+ # @param name [String, Integer]
340
359
  # @return [Object]
360
+ # @raise [TypeError] for a Symbol or another type of name.
361
+ # @raise [ArgumentError] when `name` is not a member.
341
362
  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
363
+ pair = self.class::DISPATCH_TABLE[member_name(name)]
364
+ pair[0].call(@data)
351
365
  end
352
366
 
353
367
  # @overload []=(name, val)
354
- # Sets member `name` to `val`. Unknown names fall through to
355
- # `send("#{name}=", val)`.
356
- # @param name [Symbol, String, Integer]
368
+ # Sets member `name` to `val`.
369
+ # @param name [String, Integer]
357
370
  # @param val [Object]
358
371
  # @return [Object] `val`.
372
+ # @raise [TypeError] for a Symbol or another type of name.
373
+ # @raise [ArgumentError] when `name` is not a member.
359
374
  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)
375
+ pair = self.class::DISPATCH_TABLE[member_name(name)]
376
+ pair[1].call(@data, val)
377
+ end
378
+
379
+ private def member_name (name)
380
+ case name
381
+ when Symbol
382
+ if self.class::MEMBER_TABLE.key?(name.to_s)
383
+ raise TypeError, "a struct member is named by a String (s[\"#{name}\"]) " \
384
+ "or read by its accessor (s.#{name}), not by a Symbol"
385
+ end
386
+ raise TypeError, "a struct member is named by a String, not by a Symbol " \
387
+ "(#{self.class.inspect} has no member #{name.to_s.inspect} either)"
388
+ when Integer
389
+ key = members[name]
390
+ when String
391
+ key = name
366
392
  else
367
- send(name.to_s + "=", val)
393
+ raise TypeError, "a struct member is named by a String or an Integer " \
394
+ "position (got #{name.class})"
368
395
  end
396
+ unless key && self.class::MEMBER_TABLE.key?(key)
397
+ raise ArgumentError, "#{self.class.inspect} has no member #{name.inspect} " \
398
+ "(members: #{members.inspect})"
399
+ end
400
+ key
369
401
  end
370
402
 
371
403
  # @overload each { |value| ... }
372
- # Yields each member value in declaration order.
404
+ # Yields each member value in declaration order (an Enumerator
405
+ # without a block).
373
406
  # @yieldparam value [Object]
374
407
  # @return [self]
375
408
  def each
409
+ return to_enum(:each) { length } unless block_given?
376
410
  members.each do |name|
377
411
  yield(self[name])
378
412
  end
413
+ self
379
414
  end
380
415
 
381
416
  # @overload each_pair { |name, value| ... }
@@ -385,9 +420,19 @@ class CAStruct
385
420
  # @yieldparam value [Object]
386
421
  # @return [self]
387
422
  def each_pair
423
+ return to_enum(:each_pair) { length } unless block_given?
388
424
  members.each do |name|
389
425
  yield(name.intern, self[name])
390
426
  end
427
+ self
428
+ end
429
+
430
+ # @overload to_h
431
+ # Returns the members as a Hash of member name (Symbol) to value, in
432
+ # declaration order.
433
+ # @return [Hash{Symbol => Object}]
434
+ def to_h
435
+ each_pair.to_h
391
436
  end
392
437
 
393
438
  # @overload length
@@ -432,18 +477,60 @@ class CAStruct
432
477
  return ["<", self.class.inspect, " ", table.inspect[1..-2], ">"].join
433
478
  end
434
479
 
435
- # Value comparison against another record of the same class. Unlike
436
- # `eql?`, which compares the raw bytes, this compares the decoded members.
480
+ alias to_s inspect
481
+
482
+ # Equality with another record of the same class, by the record's bytes
483
+ # (the same as {#eql?}): a struct is its bytes, so a NaN member equals
484
+ # itself, 0.0 and -0.0 differ, and so do records whose padding bytes
485
+ # differ. A record array's `eq` compares records the same way.
437
486
  # @return [Boolean]
438
487
  def == (other)
439
- case other
440
- when self.class
441
- return @data == other.__data__
442
- else
443
- return false
488
+ other.is_a?(self.class) && encode == other.encode
489
+ end
490
+
491
+ # Order against a record of the same class, by the members the struct
492
+ # was declared with in `order_by:`, compared in that order. NaN sorts
493
+ # after every number. nil when the other is not a record of this class,
494
+ # or when the struct declares no order.
495
+ # @return [Integer, nil]
496
+ def <=> (other)
497
+ keys = self.class.order_by
498
+ return nil unless keys && other.class.equal?(self.class)
499
+ keys.each do |key|
500
+ c = CAStruct.compare_key(self[key], other[key])
501
+ return c unless c == 0
502
+ end
503
+ 0
504
+ end
505
+
506
+ # @!method <(other)
507
+ # @raise [ArgumentError] when the struct declares no `order_by:`, or
508
+ # `other` is not a record of the same class.
509
+ # @!method <=(other)
510
+ # @!method >(other)
511
+ # @!method >=(other)
512
+ { :< => ->(c) { c < 0 }, :<= => ->(c) { c <= 0 },
513
+ :> => ->(c) { c > 0 }, :>= => ->(c) { c >= 0 } }.each do |op, test|
514
+ define_method(op) do |other|
515
+ c = (self <=> other)
516
+ if c.nil?
517
+ raise ArgumentError,
518
+ "comparison of #{self.class.inspect} with #{other.class.inspect} failed" +
519
+ (self.class.order_by ? "" : " (the struct declares no order_by:)")
520
+ end
521
+ test.call(c)
444
522
  end
445
523
  end
446
524
 
525
+ # One member of {#<=>}: NaN after every number, as a numeric array sorts.
526
+ def self.compare_key (a, b)
527
+ a_nan = a.is_a?(Float) && a.nan?
528
+ b_nan = b.is_a?(Float) && b.nan?
529
+ return (a_nan ? 0 : -1) if b_nan
530
+ return 1 if a_nan
531
+ a <=> b
532
+ end
533
+
447
534
  # Byte-level identity. Two CAStruct instances are eql? iff they are
448
535
  # of exactly the same class and their binary representations match.
449
536
  # Lets struct records work as Hash keys / Set members.
@@ -465,6 +552,7 @@ class CAStruct
465
552
  # @return [self]
466
553
  # @raise [CAStruct::DecodeError] on an unsupported input.
467
554
  def decode (data)
555
+ raise FrozenError.new("can't modify frozen #{self.class}", receiver: self) if frozen?
468
556
  case data
469
557
  when String
470
558
  @data.load_binary(data)
@@ -486,25 +574,18 @@ class CAStruct
486
574
  end
487
575
 
488
576
  # @overload swap_bytes!
489
- # Byte-swaps `self` in place (endian flip).
577
+ # Byte-swaps each member of `self` in place (endian flip).
490
578
  # @return [self]
491
579
  def swap_bytes!
492
- @data[] = @data.swap_bytes
493
- return self
580
+ decode(swap_bytes.encode)
494
581
  end
495
582
 
496
583
  # @overload swap_bytes
497
- # Returns a fresh byte-swapped copy of `self`.
584
+ # Returns a fresh copy of `self` with each member byte-swapped, as
585
+ # a record array's swap_bytes swaps its records.
498
586
  # @return [CAStruct]
499
587
  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
588
+ return CARecord.wrap(@data, self.class).swap_bytes[0]
508
589
  end
509
590
 
510
591
  end
@@ -527,9 +608,19 @@ class CArray
527
608
  # Returns a new {CAStruct} subclass defined by the block via
528
609
  # {CAStruct::Builder}. Options control alignment, packing, and
529
610
  # endianness.
530
- # @param opt [Hash]
611
+ #
612
+ # `order_by:` names the members records are ordered by, compared in
613
+ # that order (`order_by: [:lat, :id]` sorts by `lat`, then `id` among
614
+ # equal `lat`). It gives the records `<=>` and the comparison
615
+ # operators, and gives a {CARecord} of them `sort`, `min`, `max`,
616
+ # `partition_copy` and their index forms. A struct without it has no
617
+ # order, and those raise. NaN sorts after every number, as in a
618
+ # numeric array.
619
+ # @param opt [Hash] `pack:`, `size:`, `order_by:`.
531
620
  # @yield DSL calls in {CAStruct::Builder}.
532
621
  # @return [Class] anonymous {CAStruct} subclass.
622
+ # @raise [CAStruct::DefinitionError] when `order_by:` names something
623
+ # that is not a member, or a nested record.
533
624
  def self.struct (opt={}, &block)
534
625
  return CAStruct::Builder.new(:struct, opt).define(&block)
535
626
  end
@@ -641,4 +732,4 @@ if CArray.respond_to?(:__install_castruct_methods__)
641
732
  CArray.__install_castruct_methods__(CAStruct)
642
733
  end
643
734
 
644
-
735
+ require 'carray/record_order'
@@ -234,8 +234,17 @@ class CAStruct::Builder # :nodoc:
234
234
  end
235
235
 
236
236
  # @!visibility private
237
+ # A nested member reads as an element of the nested struct over the
238
+ # parent's own bytes (a field view), so writing to it writes to the
239
+ # parent, as writing to an array member does. A frozen parent gives a
240
+ # frozen nested element.
237
241
  def self.build_nested_struct_dispatcher (offset, nested_class)
238
- reader = ->(data) { nested_class.decode(data.field(offset, nested_class)) }
242
+ reader = ->(data) {
243
+ element = nested_class.allocate
244
+ element.instance_variable_set(:@data, data.field(offset, nested_class).parent)
245
+ element.freeze if data.frozen?
246
+ element
247
+ }
239
248
  writer = ->(data, val) { data.field(offset, nested_class)[0] = val }
240
249
  [reader, writer].freeze
241
250
  end
@@ -296,7 +305,22 @@ class CAStruct::Builder # :nodoc:
296
305
  CA_CMPLX128 => CA_ALIGN_FLOAT64,
297
306
  }
298
307
 
308
+ # Raises ArgumentError when `opt` names a key outside `allowed`. The
309
+ # options are read one key at a time, so a misspelt key would otherwise
310
+ # be ignored and the layout would change without a word.
311
+ def self.check_keys! (opt, allowed, where)
312
+ unknown = opt.keys - allowed
313
+ return if unknown.empty?
314
+ raise ArgumentError, "#{where}: unknown keyword#{unknown.size > 1 ? "s" : ""}: " +
315
+ unknown.map(&:inspect).join(", ")
316
+ end
317
+
318
+ BUILDER_KEYS = [:pack, :size, :order_by].freeze
319
+ MEMBER_KEYS = [:offset, :bytes, :endian].freeze
320
+ NESTED_KEYS = (BUILDER_KEYS + [:type, :offset]).freeze
321
+
299
322
  def initialize (type, opt = {})
323
+ CAStruct::Builder.check_keys!(opt, BUILDER_KEYS, type.to_s)
300
324
  if not opt[:pack].nil? and not opt[:pack].is_a?(Integer)
301
325
  raise CAStruct::DefinitionError,
302
326
  "invalid :pack value #{opt[:pack].inspect} (expected nil or Integer)"
@@ -305,10 +329,42 @@ class CAStruct::Builder # :nodoc:
305
329
  @align = opt[:pack] ### nil for alignment, int for pack(n)
306
330
  @members = [] ### array of CArray::Struct::Builder::Member
307
331
  @offset = 0 ### offset of each member and size of struct
332
+ @extent = 0 ### furthest byte any member reaches
308
333
  @align_max = 1 ### maximum of alignment among members
309
334
  @size = opt[:size] ### user defined struct size
310
335
  @bit_offset = 0 ### 0..7, sub-byte position for bitfield accumulator
336
+ @order_by = opt[:order_by] ### member names the records are ordered by
337
+ end
338
+
339
+ # The `order_by:` members, as member names. Each has to be a member of
340
+ # this struct that holds one ordered value: not a nested record, an array
341
+ # member or a complex number.
342
+ def order_keys (table)
343
+ keys = Array(@order_by).map(&:to_s)
344
+ if keys.empty?
345
+ raise CAStruct::DefinitionError, "order_by: names no member"
346
+ end
347
+ keys.each do |key|
348
+ unless table.key?(key)
349
+ raise CAStruct::DefinitionError, "order_by: #{key.inspect} is not a member"
350
+ end
351
+ type = table[key][1]
352
+ if type.is_a?(Class) && type <= CAStruct
353
+ raise CAStruct::DefinitionError,
354
+ "order_by: #{key.inspect} is a nested record; order by its members instead"
355
+ end
356
+ if type.is_a?(CArray)
357
+ raise CAStruct::DefinitionError,
358
+ "order_by: #{key.inspect} is an array member, which has no order"
359
+ end
360
+ if [:cmplx64, :cmplx128].include?(type)
361
+ raise CAStruct::DefinitionError,
362
+ "order_by: #{key.inspect} is complex, which has no order"
363
+ end
364
+ end
365
+ keys.freeze
311
366
  end
367
+ private :order_keys
312
368
 
313
369
  # Round up the byte cursor if the bitfield accumulator is mid-byte.
314
370
  # Called before placing a non-bit member so the byte member starts
@@ -364,6 +420,9 @@ class CAStruct::Builder # :nodoc:
364
420
  klass = Class.new(CAUnion)
365
421
  end
366
422
  # ---
423
+ # A member placed by an explicit offset can end before an earlier one
424
+ # does, so the body runs to the furthest member, not the last one.
425
+ @offset = @extent if @extent > @offset
367
426
  if @align.nil?
368
427
  @offset = alignment(@offset, :align_max)
369
428
  end
@@ -406,6 +465,7 @@ class CAStruct::Builder # :nodoc:
406
465
  names.freeze
407
466
  klass.const_set(:MEMBER_TABLE, table) ### required element as data class
408
467
  klass.const_set(:MEMBERS, names)
468
+ klass.const_set(:ORDER_BY, order_keys(table)) if @order_by
409
469
  # ---
410
470
  # Step 3 dispatch table: precompute a per-member [reader, writer]
411
471
  # Proc pair so `record[name]` is one Hash lookup + one Proc call,
@@ -424,14 +484,23 @@ class CAStruct::Builder # :nodoc:
424
484
  klass.const_set(:FAST_PRIMITIVES,
425
485
  self.class.build_fast_primitives(table, @offset).freeze)
426
486
  # ---
487
+ # An accessor for each member, unless the name is already a method of
488
+ # the element (hash, size, class, initialize, ...): the accessor would
489
+ # replace it. Such a member is read as s["name"] only, as a record
490
+ # array's members always are.
427
491
  klass.module_eval {
492
+ taken = ->(m) { method_defined?(m) || private_method_defined?(m) }
428
493
  names.each do |name|
429
- define_method(name) {
430
- return self[name]
431
- }
432
- define_method("#{name}=") { |val|
433
- return self[name] = val
434
- }
494
+ unless taken.(name)
495
+ define_method(name) {
496
+ return self[name]
497
+ }
498
+ end
499
+ unless taken.("#{name}=")
500
+ define_method("#{name}=") { |val|
501
+ return self[name] = val
502
+ }
503
+ end
435
504
  end
436
505
  }
437
506
  # ---
@@ -471,6 +540,9 @@ class CAStruct::Builder # :nodoc:
471
540
  end
472
541
 
473
542
  def pack (addr, align, opt={})
543
+ if addr < 0
544
+ raise CAStruct::DefinitionError, format("negative member offset: %d", addr)
545
+ end
474
546
  if ( addr % align ) != 0
475
547
  raise CAStruct::DefinitionError,
476
548
  format("invalid offset for packing: %d not aligned to %d", addr, align)
@@ -491,6 +563,7 @@ class CAStruct::Builder # :nodoc:
491
563
  # @param opt [Hash] member options (`:offset`, `:bytes`, `:endian`, ...).
492
564
  # @return [Member]
493
565
  def member (data_type, id = nil, opt = {})
566
+ CAStruct::Builder.check_keys!(opt, MEMBER_KEYS, "member")
494
567
  opt = opt.clone
495
568
  # If a bit member sequence was in flight, round up to a clean byte
496
569
  # boundary before placing this byte-typed member.
@@ -504,6 +577,10 @@ class CAStruct::Builder # :nodoc:
504
577
  when :struct ### struct
505
578
  case @align
506
579
  when nil ### -- aligned
580
+ if opt[:offset]
581
+ raise CAStruct::DefinitionError,
582
+ "offset: places a member only in a packed struct (pass pack:)"
583
+ end
507
584
  @offset = alignment(@offset, data_type, opt)
508
585
  opt[:offset] = @offset
509
586
  else ### -- packed
@@ -516,7 +593,12 @@ class CAStruct::Builder # :nodoc:
516
593
  mem = Member.new(id, data_type, opt)
517
594
  @members.push(mem)
518
595
  @offset += mem.byte_length
596
+ @extent = @offset if @offset > @extent
519
597
  when :union ### union
598
+ if opt[:offset] && opt[:offset] != 0
599
+ raise CAStruct::DefinitionError,
600
+ "a union member is always at offset 0 (got offset: #{opt[:offset]})"
601
+ end
520
602
  alignment(0, data_type, opt)
521
603
  opt[:offset] = 0
522
604
  mem = Member.new(id, data_type, opt)
@@ -537,17 +619,19 @@ class CAStruct::Builder # :nodoc:
537
619
  # `type:` is provided.
538
620
  def struct (*args, &block)
539
621
  opt = args.last.is_a?(Hash) ? args.pop : {}
622
+ CAStruct::Builder.check_keys!(opt, NESTED_KEYS, "struct")
540
623
  if block
541
- opt = {:pack => @align}.update(opt)
542
- st = self.class.new(:struct, opt).define(&block)
624
+ inner = {:pack => @align}.update(opt.slice(*BUILDER_KEYS))
625
+ st = self.class.new(:struct, inner).define(&block)
543
626
  elsif opt[:type] and opt[:type] <= CAStruct
544
627
  st = opt[:type]
545
628
  else
546
629
  raise CAStruct::DefinitionError,
547
630
  "no type given for nested struct member (pass a block or :type)"
548
631
  end
632
+ place = opt.key?(:offset) ? {:offset => opt[:offset]} : {}
549
633
  args.each do |arg|
550
- member(st, arg)
634
+ member(st, arg, place)
551
635
  end
552
636
  return st
553
637
  end
@@ -562,17 +646,19 @@ class CAStruct::Builder # :nodoc:
562
646
  # `type:` is provided.
563
647
  def union (*args, &block)
564
648
  opt = args.last.is_a?(Hash) ? args.pop : {}
649
+ CAStruct::Builder.check_keys!(opt, NESTED_KEYS, "union")
565
650
  if block
566
- opt = {:pack => @align}.update(opt)
567
- st = self.class.new(:union, opt).define(&block)
651
+ inner = {:pack => @align}.update(opt.slice(*BUILDER_KEYS))
652
+ st = self.class.new(:union, inner).define(&block)
568
653
  elsif opt[:type] and opt[:type] <= CAStruct
569
654
  st = opt[:type]
570
655
  else
571
656
  raise CAStruct::DefinitionError,
572
657
  "no type given for nested union member (pass a block or :type)"
573
658
  end
659
+ place = opt.key?(:offset) ? {:offset => opt[:offset]} : {}
574
660
  args.each do |arg|
575
- member(st, arg)
661
+ member(st, arg, place)
576
662
  end
577
663
  return st
578
664
  end
@@ -585,12 +671,14 @@ class CAStruct::Builder # :nodoc:
585
671
  # CArray template.
586
672
  def array (*args)
587
673
  opt = args.last.is_a?(Hash) ? args.pop : {}
674
+ CAStruct::Builder.check_keys!(opt, [:type, :offset], "array")
588
675
  if not opt[:type] or not opt[:type].kind_of?(CArray)
589
676
  raise CAStruct::DefinitionError,
590
677
  "no :type given for array member (expected a CArray template)"
591
678
  end
679
+ place = opt.key?(:offset) ? {:offset => opt[:offset]} : {}
592
680
  args.each do |arg|
593
- member(opt[:type], arg)
681
+ member(opt[:type], arg, place)
594
682
  end
595
683
  end
596
684
 
@@ -618,6 +706,13 @@ class CAStruct::Builder # :nodoc:
618
706
  "bit member requires bits: as an Integer in 1..64 (got #{bits.inspect})"
619
707
  end
620
708
  total_bit_offset = @offset * 8 + @bit_offset
709
+ # A bit member is read from one word of up to 8 bytes starting at the
710
+ # byte it begins in, so it may not reach past the 64th bit of that word.
711
+ if (total_bit_offset % 8) + bits > 64
712
+ raise CAStruct::DefinitionError,
713
+ "bit member #{name.to_s.inspect} (bits: #{bits}) starts at bit " \
714
+ "#{total_bit_offset % 8} of a byte and would span more than 8 bytes"
715
+ end
621
716
  mem = Member.new(name.to_s, :bitfield,
622
717
  {:bits => bits,
623
718
  :bit_offset => total_bit_offset,
@@ -676,6 +771,7 @@ class CAStruct::Builder # :nodoc:
676
771
  "#{typename} requires at least one member name " \
677
772
  "(use nil for anonymous padding)"
678
773
  end
774
+ CAStruct::Builder.check_keys!(opt, MEMBER_KEYS, "#{typename}")
679
775
  CAStruct::Builder.validate_endian!(opt, "#{typename}", #{allow_endian})
680
776
  args.each do |arg|
681
777
  member(:#{typename}, arg, opt)
data/lib/carray/table.rb CHANGED
@@ -16,7 +16,7 @@ class CArray
16
16
  # are silently ignored.
17
17
  # @param list [Array<Symbol, String>, nil] column names.
18
18
  # @return [Array<Symbol, String>, nil] the assigned list.
19
- # @raise [RuntimeError] when `list.size != dim1`.
19
+ # @raise [ArgumentError] when `list.size != dim1`.
20
20
  def column_names= (list)
21
21
  if self.empty?
22
22
  return
@@ -25,7 +25,7 @@ class CArray
25
25
  elsif list.empty?
26
26
  return
27
27
  elsif list and list.size != dim1
28
- raise "column_names list size is invalid (#{list.size} <=> #{dim1} )"
28
+ raise ArgumentError, "column_names list size is invalid (#{list.size} <=> #{dim1} )"
29
29
  else
30
30
  @column_names = list
31
31
  end