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
@@ -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
@@ -279,6 +288,15 @@ class CAStruct::Builder # :nodoc:
279
288
  end
280
289
 
281
290
  # @!visibility private
291
+ # Shared with every Ractor when it can be. A member whose type is an
292
+ # array template holds a CArray, which cannot be shared, and its struct
293
+ # is then used in the main Ractor only.
294
+ def self.shareable (object)
295
+ Ractor.make_shareable(object)
296
+ rescue Ractor::Error
297
+ object.freeze
298
+ end
299
+
282
300
  ALIGN_TABLE = {
283
301
  CA_FIXLEN => CA_ALIGN_FIXLEN,
284
302
  CA_BOOLEAN => CA_ALIGN_INT8,
@@ -295,8 +313,24 @@ class CAStruct::Builder # :nodoc:
295
313
  CA_CMPLX64 => CA_ALIGN_FLOAT32,
296
314
  CA_CMPLX128 => CA_ALIGN_FLOAT64,
297
315
  }
316
+ Ractor.make_shareable(ALIGN_TABLE)
317
+
318
+ # Raises ArgumentError when `opt` names a key outside `allowed`. The
319
+ # options are read one key at a time, so a misspelt key would otherwise
320
+ # be ignored and the layout would change without a word.
321
+ def self.check_keys! (opt, allowed, where)
322
+ unknown = opt.keys - allowed
323
+ return if unknown.empty?
324
+ raise ArgumentError, "#{where}: unknown keyword#{unknown.size > 1 ? "s" : ""}: " +
325
+ unknown.map(&:inspect).join(", ")
326
+ end
327
+
328
+ BUILDER_KEYS = [:pack, :size, :order_by].freeze
329
+ MEMBER_KEYS = [:offset, :bytes, :endian].freeze
330
+ NESTED_KEYS = (BUILDER_KEYS + [:type, :offset]).freeze
298
331
 
299
332
  def initialize (type, opt = {})
333
+ CAStruct::Builder.check_keys!(opt, BUILDER_KEYS, type.to_s)
300
334
  if not opt[:pack].nil? and not opt[:pack].is_a?(Integer)
301
335
  raise CAStruct::DefinitionError,
302
336
  "invalid :pack value #{opt[:pack].inspect} (expected nil or Integer)"
@@ -305,11 +339,43 @@ class CAStruct::Builder # :nodoc:
305
339
  @align = opt[:pack] ### nil for alignment, int for pack(n)
306
340
  @members = [] ### array of CArray::Struct::Builder::Member
307
341
  @offset = 0 ### offset of each member and size of struct
342
+ @extent = 0 ### furthest byte any member reaches
308
343
  @align_max = 1 ### maximum of alignment among members
309
344
  @size = opt[:size] ### user defined struct size
310
345
  @bit_offset = 0 ### 0..7, sub-byte position for bitfield accumulator
346
+ @order_by = opt[:order_by] ### member names the records are ordered by
311
347
  end
312
348
 
349
+ # The `order_by:` members, as member names. Each has to be a member of
350
+ # this struct that holds one ordered value: not a nested record, an array
351
+ # member or a complex number.
352
+ def order_keys (table)
353
+ keys = Array(@order_by).map(&:to_s)
354
+ if keys.empty?
355
+ raise CAStruct::DefinitionError, "order_by: names no member"
356
+ end
357
+ keys.each do |key|
358
+ unless table.key?(key)
359
+ raise CAStruct::DefinitionError, "order_by: #{key.inspect} is not a member"
360
+ end
361
+ type = table[key][1]
362
+ if type.is_a?(Class) && type <= CAStruct
363
+ raise CAStruct::DefinitionError,
364
+ "order_by: #{key.inspect} is a nested record; order by its members instead"
365
+ end
366
+ if type.is_a?(CArray)
367
+ raise CAStruct::DefinitionError,
368
+ "order_by: #{key.inspect} is an array member, which has no order"
369
+ end
370
+ if [:cmplx64, :cmplx128].include?(type)
371
+ raise CAStruct::DefinitionError,
372
+ "order_by: #{key.inspect} is complex, which has no order"
373
+ end
374
+ end
375
+ keys.freeze
376
+ end
377
+ private :order_keys
378
+
313
379
  # Round up the byte cursor if the bitfield accumulator is mid-byte.
314
380
  # Called before placing a non-bit member so the byte member starts
315
381
  # on a clean byte boundary (and after the body is fully built so
@@ -364,6 +430,9 @@ class CAStruct::Builder # :nodoc:
364
430
  klass = Class.new(CAUnion)
365
431
  end
366
432
  # ---
433
+ # A member placed by an explicit offset can end before an earlier one
434
+ # does, so the body runs to the furthest member, not the last one.
435
+ @offset = @extent if @extent > @offset
367
436
  if @align.nil?
368
437
  @offset = alignment(@offset, :align_max)
369
438
  end
@@ -404,8 +473,9 @@ class CAStruct::Builder # :nodoc:
404
473
  end
405
474
  table.freeze
406
475
  names.freeze
407
- klass.const_set(:MEMBER_TABLE, table) ### required element as data class
408
- klass.const_set(:MEMBERS, names)
476
+ klass.const_set(:MEMBER_TABLE, self.class.shareable(table)) ### required element as data class
477
+ klass.const_set(:MEMBERS, self.class.shareable(names))
478
+ klass.const_set(:ORDER_BY, self.class.shareable(order_keys(table))) if @order_by
409
479
  # ---
410
480
  # Step 3 dispatch table: precompute a per-member [reader, writer]
411
481
  # Proc pair so `record[name]` is one Hash lookup + one Proc call,
@@ -413,7 +483,7 @@ class CAStruct::Builder # :nodoc:
413
483
  # checks and view-shape decisions happen here, once, at class
414
484
  # definition time.
415
485
  klass.const_set(:DISPATCH_TABLE,
416
- self.class.build_dispatch_table(table, @offset).freeze)
486
+ self.class.shareable(self.class.build_dispatch_table(table, @offset)))
417
487
  # Also precompute FAST_PRIMITIVES, the subset of members that the
418
488
  # C-side `[]` / `[]=` can handle without
419
489
  # allocating a CAField view. Entries are `[offset, type_code]`;
@@ -424,14 +494,24 @@ class CAStruct::Builder # :nodoc:
424
494
  klass.const_set(:FAST_PRIMITIVES,
425
495
  self.class.build_fast_primitives(table, @offset).freeze)
426
496
  # ---
497
+ # An accessor for each member, unless the name is already a method of
498
+ # the element (hash, size, class, initialize, ...): the accessor would
499
+ # replace it. Such a member is read as s["name"] only, as a record
500
+ # array's members always are.
427
501
  klass.module_eval {
502
+ taken = ->(m) { method_defined?(m) || private_method_defined?(m) }
428
503
  names.each do |name|
429
- define_method(name) {
430
- return self[name]
431
- }
432
- define_method("#{name}=") { |val|
433
- return self[name] = val
434
- }
504
+ Ractor.make_shareable(name) # the accessors below capture it
505
+ unless taken.(name)
506
+ define_method(name, &Ractor.make_shareable(proc {
507
+ return self[name]
508
+ }))
509
+ end
510
+ unless taken.("#{name}=")
511
+ define_method("#{name}=", &Ractor.make_shareable(proc { |val|
512
+ return self[name] = val
513
+ }))
514
+ end
435
515
  end
436
516
  }
437
517
  # ---
@@ -471,6 +551,9 @@ class CAStruct::Builder # :nodoc:
471
551
  end
472
552
 
473
553
  def pack (addr, align, opt={})
554
+ if addr < 0
555
+ raise CAStruct::DefinitionError, format("negative member offset: %d", addr)
556
+ end
474
557
  if ( addr % align ) != 0
475
558
  raise CAStruct::DefinitionError,
476
559
  format("invalid offset for packing: %d not aligned to %d", addr, align)
@@ -491,6 +574,7 @@ class CAStruct::Builder # :nodoc:
491
574
  # @param opt [Hash] member options (`:offset`, `:bytes`, `:endian`, ...).
492
575
  # @return [Member]
493
576
  def member (data_type, id = nil, opt = {})
577
+ CAStruct::Builder.check_keys!(opt, MEMBER_KEYS, "member")
494
578
  opt = opt.clone
495
579
  # If a bit member sequence was in flight, round up to a clean byte
496
580
  # boundary before placing this byte-typed member.
@@ -504,6 +588,10 @@ class CAStruct::Builder # :nodoc:
504
588
  when :struct ### struct
505
589
  case @align
506
590
  when nil ### -- aligned
591
+ if opt[:offset]
592
+ raise CAStruct::DefinitionError,
593
+ "offset: places a member only in a packed struct (pass pack:)"
594
+ end
507
595
  @offset = alignment(@offset, data_type, opt)
508
596
  opt[:offset] = @offset
509
597
  else ### -- packed
@@ -516,7 +604,12 @@ class CAStruct::Builder # :nodoc:
516
604
  mem = Member.new(id, data_type, opt)
517
605
  @members.push(mem)
518
606
  @offset += mem.byte_length
607
+ @extent = @offset if @offset > @extent
519
608
  when :union ### union
609
+ if opt[:offset] && opt[:offset] != 0
610
+ raise CAStruct::DefinitionError,
611
+ "a union member is always at offset 0 (got offset: #{opt[:offset]})"
612
+ end
520
613
  alignment(0, data_type, opt)
521
614
  opt[:offset] = 0
522
615
  mem = Member.new(id, data_type, opt)
@@ -537,17 +630,19 @@ class CAStruct::Builder # :nodoc:
537
630
  # `type:` is provided.
538
631
  def struct (*args, &block)
539
632
  opt = args.last.is_a?(Hash) ? args.pop : {}
633
+ CAStruct::Builder.check_keys!(opt, NESTED_KEYS, "struct")
540
634
  if block
541
- opt = {:pack => @align}.update(opt)
542
- st = self.class.new(:struct, opt).define(&block)
635
+ inner = {:pack => @align}.update(opt.slice(*BUILDER_KEYS))
636
+ st = self.class.new(:struct, inner).define(&block)
543
637
  elsif opt[:type] and opt[:type] <= CAStruct
544
638
  st = opt[:type]
545
639
  else
546
640
  raise CAStruct::DefinitionError,
547
641
  "no type given for nested struct member (pass a block or :type)"
548
642
  end
643
+ place = opt.key?(:offset) ? {:offset => opt[:offset]} : {}
549
644
  args.each do |arg|
550
- member(st, arg)
645
+ member(st, arg, place)
551
646
  end
552
647
  return st
553
648
  end
@@ -562,17 +657,19 @@ class CAStruct::Builder # :nodoc:
562
657
  # `type:` is provided.
563
658
  def union (*args, &block)
564
659
  opt = args.last.is_a?(Hash) ? args.pop : {}
660
+ CAStruct::Builder.check_keys!(opt, NESTED_KEYS, "union")
565
661
  if block
566
- opt = {:pack => @align}.update(opt)
567
- st = self.class.new(:union, opt).define(&block)
662
+ inner = {:pack => @align}.update(opt.slice(*BUILDER_KEYS))
663
+ st = self.class.new(:union, inner).define(&block)
568
664
  elsif opt[:type] and opt[:type] <= CAStruct
569
665
  st = opt[:type]
570
666
  else
571
667
  raise CAStruct::DefinitionError,
572
668
  "no type given for nested union member (pass a block or :type)"
573
669
  end
670
+ place = opt.key?(:offset) ? {:offset => opt[:offset]} : {}
574
671
  args.each do |arg|
575
- member(st, arg)
672
+ member(st, arg, place)
576
673
  end
577
674
  return st
578
675
  end
@@ -585,12 +682,14 @@ class CAStruct::Builder # :nodoc:
585
682
  # CArray template.
586
683
  def array (*args)
587
684
  opt = args.last.is_a?(Hash) ? args.pop : {}
685
+ CAStruct::Builder.check_keys!(opt, [:type, :offset], "array")
588
686
  if not opt[:type] or not opt[:type].kind_of?(CArray)
589
687
  raise CAStruct::DefinitionError,
590
688
  "no :type given for array member (expected a CArray template)"
591
689
  end
690
+ place = opt.key?(:offset) ? {:offset => opt[:offset]} : {}
592
691
  args.each do |arg|
593
- member(opt[:type], arg)
692
+ member(opt[:type], arg, place)
594
693
  end
595
694
  end
596
695
 
@@ -618,6 +717,13 @@ class CAStruct::Builder # :nodoc:
618
717
  "bit member requires bits: as an Integer in 1..64 (got #{bits.inspect})"
619
718
  end
620
719
  total_bit_offset = @offset * 8 + @bit_offset
720
+ # A bit member is read from one word of up to 8 bytes starting at the
721
+ # byte it begins in, so it may not reach past the 64th bit of that word.
722
+ if (total_bit_offset % 8) + bits > 64
723
+ raise CAStruct::DefinitionError,
724
+ "bit member #{name.to_s.inspect} (bits: #{bits}) starts at bit " \
725
+ "#{total_bit_offset % 8} of a byte and would span more than 8 bytes"
726
+ end
621
727
  mem = Member.new(name.to_s, :bitfield,
622
728
  {:bits => bits,
623
729
  :bit_offset => total_bit_offset,
@@ -676,6 +782,7 @@ class CAStruct::Builder # :nodoc:
676
782
  "#{typename} requires at least one member name " \
677
783
  "(use nil for anonymous padding)"
678
784
  end
785
+ CAStruct::Builder.check_keys!(opt, MEMBER_KEYS, "#{typename}")
679
786
  CAStruct::Builder.validate_endian!(opt, "#{typename}", #{allow_endian})
680
787
  args.each do |arg|
681
788
  member(:#{typename}, arg, opt)