carray 2.0.0 → 3.0.0

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 (339) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -25
  3. data/CHANGELOG.md +16 -0
  4. data/LICENSE +1 -1
  5. data/NEWS.md +3 -0
  6. data/README.md +128 -44
  7. data/carray.gemspec +22 -24
  8. data/ext/ca_array_pool.c +91 -0
  9. data/ext/ca_axis_descriptor.h +186 -0
  10. data/ext/ca_axis_dispatch.c +924 -0
  11. data/ext/ca_axis_group.c +1208 -0
  12. data/ext/ca_bincmp_dispatch.c +76 -0
  13. data/ext/ca_bincmp_dispatch.h +85 -0
  14. data/ext/ca_binop_dispatch.c +125 -0
  15. data/ext/ca_binop_dispatch.h +159 -0
  16. data/ext/ca_categorical_iterator.c +1375 -0
  17. data/ext/ca_compare.c +94 -0
  18. data/ext/ca_compare.h +26 -0
  19. data/ext/ca_composite_dispatch.c +414 -0
  20. data/ext/ca_composite_dispatch.h +116 -0
  21. data/ext/ca_for_buffer.h +96 -0
  22. data/ext/ca_for_each_element.h +241 -0
  23. data/ext/ca_group_iter.c +304 -0
  24. data/ext/ca_iter_substrate.h +325 -0
  25. data/ext/ca_kernel_iterator.c +4321 -0
  26. data/ext/ca_kernel_iterator.h +2603 -0
  27. data/ext/ca_moncmp_dispatch.c +37 -0
  28. data/ext/ca_moncmp_dispatch.h +62 -0
  29. data/ext/ca_monop_dispatch.c +200 -0
  30. data/ext/ca_monop_dispatch.h +235 -0
  31. data/ext/ca_obj_array.c +355 -359
  32. data/ext/ca_obj_bincmp.c +809 -0
  33. data/ext/ca_obj_binop.c +892 -0
  34. data/ext/ca_obj_bitarray.c +369 -164
  35. data/ext/ca_obj_bitfield.c +294 -234
  36. data/ext/ca_obj_block.c +189 -711
  37. data/ext/ca_obj_byte_swap.c +766 -0
  38. data/ext/ca_obj_const_string.c +965 -0
  39. data/ext/ca_obj_face.c +670 -0
  40. data/ext/ca_obj_face.h +247 -0
  41. data/ext/ca_obj_fake.c +228 -100
  42. data/ext/ca_obj_farray.c +54 -441
  43. data/ext/ca_obj_field.c +82 -529
  44. data/ext/ca_obj_fixlen_string.c +306 -0
  45. data/ext/ca_obj_grid.c +858 -440
  46. data/ext/ca_obj_meld.c +1034 -0
  47. data/ext/ca_obj_moncmp.c +569 -0
  48. data/ext/ca_obj_monop.c +1111 -0
  49. data/ext/ca_obj_object.c +774 -298
  50. data/ext/ca_obj_record.c +468 -0
  51. data/ext/ca_obj_reduce.c +97 -82
  52. data/ext/ca_obj_refer.c +569 -459
  53. data/ext/ca_obj_remap.c +475 -0
  54. data/ext/ca_obj_repeat.c +92 -477
  55. data/ext/ca_obj_roll.c +616 -0
  56. data/ext/ca_obj_select.c +344 -296
  57. data/ext/ca_obj_select_axis.c +1296 -0
  58. data/ext/ca_obj_shift.c +230 -792
  59. data/ext/ca_obj_source.c +78 -0
  60. data/ext/ca_obj_stack.c +1173 -0
  61. data/ext/ca_obj_stride.c +2501 -0
  62. data/ext/ca_obj_string.c +268 -0
  63. data/ext/ca_obj_tile.c +614 -0
  64. data/ext/ca_obj_time.c +546 -0
  65. data/ext/ca_obj_timedelta.c +435 -0
  66. data/ext/ca_obj_transpose.c +62 -516
  67. data/ext/ca_obj_triop.c +746 -0
  68. data/ext/ca_obj_unbound_repeat.c +208 -241
  69. data/ext/ca_obj_window.c +1131 -563
  70. data/ext/ca_op_byte_swap.c +175 -0
  71. data/ext/ca_op_ipower.c +319 -0
  72. data/ext/ca_op_powi.h +88 -0
  73. data/ext/ca_sort_kernels.h +132 -0
  74. data/ext/ca_sweep_engine.c +430 -0
  75. data/ext/ca_sweep_engine.h +157 -0
  76. data/ext/ca_transform_common.c +228 -0
  77. data/ext/ca_triop_dispatch.c +55 -0
  78. data/ext/ca_triop_dispatch.h +62 -0
  79. data/ext/carray.h +795 -402
  80. data/ext/carray_access.c +831 -711
  81. data/ext/carray_attribute.c +98 -330
  82. data/ext/carray_bincount.c +255 -0
  83. data/ext/carray_broadcast.c +283 -0
  84. data/ext/carray_call_cfunc.c +1360 -828
  85. data/ext/carray_call_cfunc.h +160 -0
  86. data/ext/carray_cast.c +1212 -301
  87. data/ext/carray_cast_func.rb +81 -40
  88. data/ext/carray_class.c +53 -63
  89. data/ext/carray_config.h +28 -0
  90. data/ext/carray_conversion.c +350 -346
  91. data/ext/carray_copy.c +156 -268
  92. data/ext/carray_core.c +1342 -199
  93. data/ext/carray_count.c +312 -0
  94. data/ext/carray_data_type.c +43 -19
  95. data/ext/carray_element.c +585 -213
  96. data/ext/carray_factorize.c +2542 -0
  97. data/ext/carray_generate.c +230 -559
  98. data/ext/carray_histogram.c +490 -0
  99. data/ext/carray_hold.c +228 -0
  100. data/ext/carray_index_classifier.c +1035 -0
  101. data/ext/carray_index_classifier.h +27 -0
  102. data/ext/carray_internal.h +120 -0
  103. data/ext/carray_kernels_bincmp.c +4445 -0
  104. data/ext/carray_kernels_binop.c +10979 -0
  105. data/ext/carray_kernels_init.c +36 -0
  106. data/ext/carray_kernels_map.c +3466 -0
  107. data/ext/carray_kernels_moncmp.c +2096 -0
  108. data/ext/carray_kernels_monop.c +18312 -0
  109. data/ext/carray_kernels_reduce_aggregate.c +25836 -0
  110. data/ext/carray_kernels_reduce_boolean.c +329 -0
  111. data/ext/carray_kernels_reduce_cumulative.c +14592 -0
  112. data/ext/carray_kernels_reduce_extreme.c +16947 -0
  113. data/ext/carray_kernels_reduce_variance.c +3909 -0
  114. data/ext/carray_kernels_scan.c +3692 -0
  115. data/ext/carray_kernels_search.c +32137 -0
  116. data/ext/carray_kernels_sort.c +10625 -0
  117. data/ext/carray_kernels_triop.c +1391 -0
  118. data/ext/carray_lazy.c +567 -0
  119. data/ext/carray_loop.c +88 -200
  120. data/ext/carray_mask.c +848 -154
  121. data/ext/carray_math_kernel.h +120 -0
  122. data/ext/carray_mathfunc.c +10 -241
  123. data/ext/carray_median_percentile.c +1257 -0
  124. data/ext/carray_memory_view.c +1625 -0
  125. data/ext/carray_operator.c +1526 -318
  126. data/ext/carray_order.c +664 -1394
  127. data/ext/carray_partition.c +416 -0
  128. data/ext/carray_random.c +518 -0
  129. data/ext/carray_scatter.c +357 -0
  130. data/ext/carray_slab.c +1219 -0
  131. data/ext/carray_slab.h +84 -0
  132. data/ext/carray_sort.c +829 -0
  133. data/ext/carray_sort_kernel.c +620 -0
  134. data/ext/carray_struct.c +695 -0
  135. data/ext/carray_test.c +343 -229
  136. data/ext/carray_undef.c +34 -17
  137. data/ext/carray_utils.c +175 -74
  138. data/ext/extconf.rb +216 -55
  139. data/ext/mk_call_cfunc.rb +480 -0
  140. data/ext/mkkernel.rb +8842 -0
  141. data/ext/ruby_carray.c +202 -101
  142. data/ext/version.h +4 -14
  143. data/ext/version.rb +5 -13
  144. data/lib/carray/arrow_tensor.rb +401 -0
  145. data/lib/carray/attribute.rb +166 -0
  146. data/lib/carray/autoload_carray.rb +220 -0
  147. data/lib/carray/autoload_method_extension.rb +44 -0
  148. data/lib/carray/axis_group.rb +711 -0
  149. data/lib/carray/basics.rb +481 -0
  150. data/lib/carray/bincount_nd.rb +358 -0
  151. data/lib/carray/block_iterator.rb +604 -0
  152. data/lib/carray/boolean_reduce.rb +109 -0
  153. data/lib/carray/categorical.rb +561 -0
  154. data/lib/carray/categorical_iterator.rb +1062 -0
  155. data/lib/carray/complex.rb +150 -0
  156. data/lib/carray/conditional.rb +216 -0
  157. data/lib/carray/const_string.rb +228 -0
  158. data/lib/carray/construct.rb +139 -328
  159. data/lib/carray/core_extensions.rb +240 -0
  160. data/lib/carray/data_type_extension.rb +233 -0
  161. data/lib/carray/fixlen_string.rb +95 -0
  162. data/lib/carray/frame/concat.rb +132 -0
  163. data/lib/carray/frame/convert.rb +95 -0
  164. data/lib/carray/frame/csv_parser.rb +211 -0
  165. data/lib/carray/frame/frame.rb +649 -0
  166. data/lib/carray/frame/group.rb +186 -0
  167. data/lib/carray/frame/io.rb +164 -0
  168. data/lib/carray/frame/join.rb +248 -0
  169. data/lib/carray/frame/records.rb +99 -0
  170. data/lib/carray/frame/sort.rb +113 -0
  171. data/lib/carray/frame/verbs.rb +299 -0
  172. data/lib/carray/frame.rb +16 -0
  173. data/lib/carray/histogram.rb +512 -0
  174. data/lib/carray/inspect.rb +37 -20
  175. data/lib/carray/iterator.rb +57 -349
  176. data/lib/carray/lazy.rb +889 -0
  177. data/lib/carray/mask_gap_fill.rb +200 -0
  178. data/lib/carray/math.rb +78 -342
  179. data/lib/carray/meld_reduce.rb +289 -0
  180. data/lib/carray/methods/align_addr.rb +116 -0
  181. data/lib/carray/methods/bin.rb +128 -0
  182. data/lib/carray/methods/bincount.rb +87 -0
  183. data/lib/carray/methods/bit_string.rb +92 -0
  184. data/lib/carray/methods/broadcast.rb +63 -0
  185. data/lib/carray/methods/choose.rb +39 -0
  186. data/lib/carray/methods/composition.rb +280 -0
  187. data/lib/carray/methods/gather_nd.rb +206 -0
  188. data/lib/carray/methods/index.rb +39 -0
  189. data/lib/carray/methods/insert_block.rb +99 -0
  190. data/lib/carray/methods/is_in.rb +141 -0
  191. data/lib/carray/methods/join.rb +90 -0
  192. data/lib/carray/methods/locate_addr.rb +47 -0
  193. data/lib/carray/methods/mask_duplicates.rb +41 -0
  194. data/lib/carray/methods/meshgrid.rb +91 -0
  195. data/lib/carray/methods/mode.rb +126 -0
  196. data/lib/carray/methods/nunique.rb +46 -0
  197. data/lib/carray/methods/resize.rb +56 -0
  198. data/lib/carray/methods/snap.rb +156 -0
  199. data/lib/carray/methods/string_format.rb +57 -0
  200. data/lib/carray/methods/unique.rb +47 -0
  201. data/lib/carray/methods/value_counts.rb +71 -0
  202. data/lib/carray/mkmf.rb +124 -101
  203. data/lib/carray/runtime.rb +108 -0
  204. data/lib/carray/serialize.rb +478 -167
  205. data/lib/carray/slab_iterator.rb +292 -0
  206. data/lib/carray/stack.rb +291 -0
  207. data/lib/carray/string.rb +56 -180
  208. data/lib/carray/string_operation_extension.rb +289 -0
  209. data/lib/carray/struct.rb +335 -323
  210. data/lib/carray/struct_builder.rb +697 -0
  211. data/lib/carray/table.rb +41 -2
  212. data/lib/carray/time.rb +2255 -38
  213. data/lib/carray/window_iterator.rb +655 -0
  214. data/lib/carray.rb +55 -57
  215. metadata +163 -130
  216. data/Rakefile +0 -51
  217. data/TODO.md +0 -18
  218. data/ext/ca_iter_block.c +0 -257
  219. data/ext/ca_iter_dimension.c +0 -299
  220. data/ext/ca_iter_window.c +0 -214
  221. data/ext/ca_obj_mapping.c +0 -644
  222. data/ext/carray_iterator.c +0 -641
  223. data/ext/carray_math.rb +0 -850
  224. data/ext/carray_numeric.c +0 -259
  225. data/ext/carray_sort_addr.c +0 -254
  226. data/ext/carray_stat.c +0 -2100
  227. data/ext/carray_stat_proc.rb +0 -1999
  228. data/ext/mkmath.rb +0 -741
  229. data/ext/ruby_ccomplex.c +0 -509
  230. data/ext/ruby_float_func.c +0 -86
  231. data/lib/carray/array.rb +0 -8
  232. data/lib/carray/autoload/autoload_base.rb +0 -19
  233. data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
  234. data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
  235. data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
  236. data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
  237. data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
  238. data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
  239. data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
  240. data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
  241. data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
  242. data/lib/carray/autoload/autoload_gem_random.rb +0 -8
  243. data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
  244. data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
  245. data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
  246. data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
  247. data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
  248. data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
  249. data/lib/carray/autoload/autoload_object_link.rb +0 -1
  250. data/lib/carray/autoload/autoload_object_pack.rb +0 -2
  251. data/lib/carray/autoload.rb +0 -141
  252. data/lib/carray/basic.rb +0 -191
  253. data/lib/carray/broadcast.rb +0 -101
  254. data/lib/carray/compose.rb +0 -315
  255. data/lib/carray/convert.rb +0 -115
  256. data/lib/carray/info.rb +0 -110
  257. data/lib/carray/io/imagemagick.rb +0 -235
  258. data/lib/carray/mask.rb +0 -102
  259. data/lib/carray/math/histogram.rb +0 -177
  260. data/lib/carray/math/recurrence.rb +0 -93
  261. data/lib/carray/object/ca_obj_iterator.rb +0 -50
  262. data/lib/carray/object/ca_obj_link.rb +0 -50
  263. data/lib/carray/object/ca_obj_pack.rb +0 -99
  264. data/lib/carray/obsolete.rb +0 -256
  265. data/lib/carray/ordering.rb +0 -181
  266. data/lib/carray/testing.rb +0 -51
  267. data/lib/carray/transform.rb +0 -109
  268. data/misc/Methods.ja.md +0 -182
  269. data/misc/NOTE +0 -51
  270. data/spec/Classes/CABitfield_spec.rb +0 -58
  271. data/spec/Classes/CABlockIterator_spec.rb +0 -114
  272. data/spec/Classes/CABlock_spec.rb +0 -205
  273. data/spec/Classes/CAField_spec.rb +0 -39
  274. data/spec/Classes/CAGrid_spec.rb +0 -75
  275. data/spec/Classes/CAMap_spec.rb +0 -0
  276. data/spec/Classes/CAMapping_spec.rb +0 -105
  277. data/spec/Classes/CAObject_attribute_spec.rb +0 -33
  278. data/spec/Classes/CAObject_spec.rb +0 -33
  279. data/spec/Classes/CARefer_spec.rb +0 -93
  280. data/spec/Classes/CARepeat_spec.rb +0 -65
  281. data/spec/Classes/CASelect_spec.rb +0 -22
  282. data/spec/Classes/CAShift_spec.rb +0 -16
  283. data/spec/Classes/CAStruct_spec.rb +0 -71
  284. data/spec/Classes/CATranspose_spec.rb +0 -60
  285. data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
  286. data/spec/Classes/CAWindow_spec.rb +0 -54
  287. data/spec/Classes/CAWrap_spec.rb +0 -8
  288. data/spec/Classes/CArray_spec.rb +0 -184
  289. data/spec/Classes/CScalar_spec.rb +0 -55
  290. data/spec/Classes/ex1.rb +0 -46
  291. data/spec/Features/feature_130_spec.rb +0 -19
  292. data/spec/Features/feature_attributes_spec.rb +0 -280
  293. data/spec/Features/feature_boolean_spec.rb +0 -98
  294. data/spec/Features/feature_broadcast.rb +0 -116
  295. data/spec/Features/feature_cast_function.rb +0 -19
  296. data/spec/Features/feature_cast_spec.rb +0 -33
  297. data/spec/Features/feature_class_spec.rb +0 -84
  298. data/spec/Features/feature_complex_spec.rb +0 -42
  299. data/spec/Features/feature_composite_spec.rb +0 -124
  300. data/spec/Features/feature_convert_spec.rb +0 -46
  301. data/spec/Features/feature_copy_spec.rb +0 -123
  302. data/spec/Features/feature_creation_spec.rb +0 -84
  303. data/spec/Features/feature_element_spec.rb +0 -144
  304. data/spec/Features/feature_extream_spec.rb +0 -54
  305. data/spec/Features/feature_generate_spec.rb +0 -74
  306. data/spec/Features/feature_index_spec.rb +0 -69
  307. data/spec/Features/feature_mask_spec.rb +0 -580
  308. data/spec/Features/feature_math_spec.rb +0 -97
  309. data/spec/Features/feature_order_spec.rb +0 -146
  310. data/spec/Features/feature_ref_store_spec.rb +0 -209
  311. data/spec/Features/feature_serialization_spec.rb +0 -125
  312. data/spec/Features/feature_stat_spec.rb +0 -397
  313. data/spec/Features/feature_virtual_spec.rb +0 -48
  314. data/spec/Features/method_eq_spec.rb +0 -81
  315. data/spec/Features/method_is_nan_spec.rb +0 -12
  316. data/spec/Features/method_map_spec.rb +0 -54
  317. data/spec/Features/method_max_with.rb +0 -20
  318. data/spec/Features/method_min_with.rb +0 -19
  319. data/spec/Features/method_ne_spec.rb +0 -18
  320. data/spec/Features/method_project_spec.rb +0 -188
  321. data/spec/Features/method_ref_spec.rb +0 -27
  322. data/spec/Features/method_round_spec.rb +0 -11
  323. data/spec/Features/method_s_linspace_spec.rb +0 -48
  324. data/spec/Features/method_s_span_spec.rb +0 -14
  325. data/spec/Features/method_seq_spec.rb +0 -47
  326. data/spec/Features/method_sort_with.rb +0 -43
  327. data/spec/Features/method_sorted_with.rb +0 -29
  328. data/spec/Features/method_span_spec.rb +0 -42
  329. data/spec/Features/method_wrap_readonly_spec.rb +0 -43
  330. data/spec/UnitTest/test_CAVirtual.rb +0 -214
  331. data/spec/spec_all.rb +0 -10
  332. data/utils/ca_ase.rb +0 -21
  333. data/utils/ca_methods.rb +0 -15
  334. data/utils/cast_checker.rb +0 -30
  335. data/utils/convert_test.rb +0 -73
  336. data/utils/extract_yard.rb +0 -22
  337. data/utils/guess_shape.rb +0 -76
  338. data/utils/monkey_patch_methods.rb +0 -62
  339. data/utils/remove_resource_fork.sh +0 -5
@@ -1,80 +1,103 @@
1
- # ----------------------------------------------------------------------------
1
+ # Portable binary serialization ("_CARRAY3" format).
2
2
  #
3
- # carray/serialize.rb
3
+ # User-facing spec (C struct / FORTRAN / NumPy readers):
4
+ # docs/topics/Serialization.md.
4
5
  #
5
- # This file is part of Ruby/CArray extension library.
6
+ # Public surface:
7
+ # CArray.save(ca, path_or_io, endian: CArray.endian)
8
+ # CArray.load(path_or_io_or_string)
9
+ # CArray.dump(ca, endian: CArray.endian) # -> String
10
+ # CArray#marshal_dump / CArray#marshal_load
6
11
  #
7
- # Copyright (C) 2005-2020 Hiroki Motoyoshi
12
+ # Core promise: numeric payload lives at a fixed offset (data_offset,
13
+ # 256 in v1.x) as raw contiguous bytes in a single file-wide endian.
14
+ # A C / FORTRAN / Python reader reaches the data with one seek and
15
+ # never parses any Ruby-specific metadata. CArray-native concepts
16
+ # (attribute Hash, data_class Face schema, mask) ride in a YAML
17
+ # flow-style trailer at the file tail that other languages skip.
8
18
  #
9
- # ----------------------------------------------------------------------------
19
+ # CA_OBJECT (arbitrary Ruby objects) is *not* serialisable by this
20
+ # portable format: CArray.save raises on it. Object arrays persist
21
+ # through the separate Ruby-only Marshal path (CArray#marshal_dump).
10
22
  #
23
+ # On-disk layout (all header integers in file endian; the byte order is
24
+ # self-describing via endian_marker at offset 8):
11
25
  #
12
- # CArray.save(ca, filename, {:endian=>CArray.endian})
13
- # CArray.save(ca, io, {:endian=>CArray.endian})
26
+ # 0-7 magic char*8 "_CARRAY3"
27
+ # 8-11 endian_marker uint32 0x01020304 in file endian
28
+ # 12 version_major uint8 1
29
+ # 13 version_minor uint8 0
30
+ # 14-15 header_bytes uint16 256
31
+ # 16 has_mask uint8
32
+ # 17 has_trailer uint8
33
+ # 18 data_type_code uint8 CArray CA_* code
34
+ # 19 ndim uint8
35
+ # 20-23 reserved0 uint32 zero (align shape to 8 bytes)
36
+ # 24-151 shape int64*16 front ndim slots valid
37
+ # 152-155 element_bytes uint32
38
+ # 156-159 flags int32 raw ca->flags snapshot (provenance)
39
+ # 160-167 elements uint64
40
+ # 168-175 data_offset uint64 = 256
41
+ # 176-183 data_bytes uint64 = elements*element_bytes
42
+ # 184-191 mask_offset uint64 0 if !has_mask
43
+ # 192-199 mask_bytes uint64 0 if !has_mask
44
+ # 200-207 trailer_offset uint64 0 if !has_trailer
45
+ # 208-215 trailer_bytes uint64 0 if !has_trailer
46
+ # 216-223 data_checksum uint64 reserved (0)
47
+ # 224 checksum_algo uint8 reserved (0=none)
48
+ # 225-255 reserved zero
14
49
  #
15
- # CArray.load(filename)
16
- # CArray.load(io)
50
+ # 256-... Data (raw contiguous, file endian ordering)
51
+ # ... Mask (raw int8*elements, if has_mask)
52
+ # ... Trailer (UTF-8 YAML flow, trailer_bytes long, if present)
17
53
  #
18
- # CArray.dump(filename, {:endian=>CArray.endian})
19
- #
20
- # CArray#marshal_dump
21
- # CArray#marshal_load(data)
22
- #
23
-
24
- #
25
- # CArray's Binary Format
26
- #
27
- # offset 0 bytes
28
- # magic_string : char*8 : "_CARRAY_"
29
- # data_type_name : char*8 : e.g. "int8", "cmplx256" ...
30
- # endian : char*4 : "_LE_":LITTLE_ENIDAN or "_BE_":BIG_ENDIAN
31
- # offset 20 bytes
32
- # data_type : int32 : data type specifier
33
- # bytes : int32 : byte size of each data
34
- # ndim : int32 : ndim of array
35
- # elements : int32 : number of all elements
36
- # has_mask : int32 : 0 (not masked) or 1 (masked)
37
- # offset 40 bytes
38
- # dim[CA_RANK_MAX] : int32 : size for 0-th dimension
39
- # has_attribute : int32
40
- #
41
- # offset 256 bytes
42
- # data : bytes*elements : value data
43
- # mask : int8*elements : mask data if has_mask == 1
44
- # attribute : object marshal : attribute
45
- # data_class_name : string marshal : data_class_name
54
+ # flags is a verbatim snapshot of the source array's internal
55
+ # ca->flags at write time, kept purely as a provenance record of what
56
+ # the array was (scalar / Face / view / read-only / ...). It is NOT
57
+ # read back on load and carries no reconstruction meaning.
46
58
 
47
59
  require "stringio"
60
+ require "psych"
48
61
 
49
- class CArray::Serializer # :nodoc:
50
-
51
- Header = CA.struct(:pack=>1, :size=>256) {
52
- char_p :magic_string, :bytes=>8
53
- char_p :data_type_name, :bytes=>8
54
- char_p :endian, :bytes=>4
55
- int32 :data_type
56
- int64 :bytes
57
- int32 :ndim
58
- int64 :elements
59
- int32 :has_mask
60
- array :dim, :type => CArray.int64(CA_RANK_MAX)
61
- int32 :has_attr
62
- int32 :has_data_class
63
- }
64
-
65
- Header_Legacy = CA.struct(:pack=>1, :size=>256) {
66
- char_p :magic_string, :bytes=>8
67
- char_p :data_type_name, :bytes=>8
68
- char_p :endian, :bytes=>4
69
- int32 :data_type
70
- int32 :bytes
71
- int32 :ndim
72
- int32 :elements
73
- int32 :has_mask
74
- array :dim, :type => CArray.int32(CA_RANK_MAX)
75
- int32 :has_attr
76
- }
62
+ class CArray::Serializer # :nodoc:
77
63
 
64
+ # @!visibility private
65
+ MAGIC = "_CARRAY3"
66
+ # @!visibility private
67
+ HEADER_BYTES = 256
68
+ # @!visibility private
69
+ VERSION_MAJOR = 1
70
+ # @!visibility private
71
+ VERSION_MINOR = 0
72
+ # @!visibility private
73
+ ENDIAN_MARKER = 0x01020304
74
+
75
+ # Canonical primitive-type notation for the trailer data_class
76
+ # schema. The .ca format *owns* this mapping: it is the same bare
77
+ # PEP 3118 notation CArray's MemoryView layer emits via
78
+ # ca_mv_format_for, frozen here as the format's own copy so a future
79
+ # MemoryView producer flip does not silently change the on-disk spec.
80
+ # Non-primitive member types (:fixlen / :object / nested class /
81
+ # CArray template / :bitfield) are absent, so SYMBOL_TO_PEP[type] is
82
+ # nil for them, which enforces the flat-primitive-only rule for free
83
+ # (a non-expressible member raises on save).
84
+ PEP_TO_SYMBOL = {
85
+ "?" => :boolean,
86
+ "b" => :int8, "B" => :uint8,
87
+ "h" => :int16, "H" => :uint16,
88
+ "i" => :int32, "I" => :uint32,
89
+ "q" => :int64, "Q" => :uint64,
90
+ "f" => :float32, "d" => :float64,
91
+ "Zf" => :cmplx64, "Zd" => :cmplx128,
92
+ }.freeze
93
+
94
+ # @!visibility private
95
+ SYMBOL_TO_PEP = PEP_TO_SYMBOL.invert.freeze
96
+
97
+ # @overload initialize(io)
98
+ # Allocates a Serializer around `io`. A String is wrapped in a
99
+ # `StringIO`; anything else is used directly.
100
+ # @param io [IO, String] destination or source.
78
101
  def initialize (io)
79
102
  case io
80
103
  when String
@@ -84,128 +107,377 @@ class CArray::Serializer # :nodoc:
84
107
  end
85
108
  end
86
109
 
110
+ # @overload save(ca, endian: CArray.endian)
111
+ # Writes `ca` to the wrapped IO in the _CARRAY3 portable format.
112
+ # The data region is raw contiguous bytes; attribute Hash and
113
+ # data_class schema (if any) ride in a YAML trailer at the tail.
114
+ # @param ca [CArray] array to write.
115
+ # @param opt [Hash] `:endian` forces the output byte order
116
+ # (defaults to the host's; a differing value swaps data + header).
117
+ # @return [CArray] `ca`.
118
+ # @raise [ArgumentError] when `ca` is a CA_OBJECT array (use
119
+ # `Marshal.dump(ca)` instead), or carries a data_class the
120
+ # v1.0 flat-primitive schema cannot express.
87
121
  def save (ca, **opt)
88
- endian = opt[:endian] || CArray.endian
89
- # ---
90
- header = Header.new()
91
- header[:magic_string] = "_CARRAY_"
92
- header[:data_type_name] = ca.data_type_name.ljust(8)
93
- header[:endian] = ( endian == CA_LITTLE_ENDIAN ) ? "_LE_" : "_BE_"
94
- header[:data_type] = ca.data_type
95
- header[:bytes] = ca.bytes
96
- header[:ndim] = ca.ndim
97
- header[:elements] = ca.elements
98
- header[:has_mask] = ca.has_mask? ? 1 : 0
99
- header[:has_data_class] = ca.has_data_class? ? 1 : 0
100
- header[:dim][[0,ca.ndim]] = ca.dim
101
- attributes = nil
102
- if ca.attribute
103
- attributes = ca.attribute.clone
104
- end
105
- if opt[:attribute]
106
- (attributes ||= {}).update(opt[:attribute])
107
- end
108
- header[:has_attr] = attributes.empty? ? 0 : 1
109
- unless CArray.endian == endian
110
- header.swap_bytes!
111
- end
112
- @io.write(header.encode)
113
- # ---
114
- if ca.data_type == CA_OBJECT
115
- Marshal.dump(ca.value.to_a, @io)
116
- else
117
- unless CArray.endian == endian
118
- ca = ca.swap_bytes
119
- end
120
- ca.dump_binary(@io)
121
- end
122
- # ---
123
- if ca.has_mask?
124
- ca.mask.dump_binary(@io)
122
+ if ca.data_type == :object
123
+ raise ArgumentError,
124
+ "CArray.save cannot serialise a CA_OBJECT array " \
125
+ "(arbitrary Ruby objects have no portable representation); " \
126
+ "use Marshal.dump(ca) for a Ruby-only round-trip"
125
127
  end
126
- if attributes
127
- Marshal.dump(attributes, @io)
128
- end
129
- if ca.has_data_class?
130
- Marshal.dump(ca.data_class.to_s, @io)
128
+
129
+ file_endian = opt[:endian] || CArray.endian
130
+ swap = (file_endian != CArray.endian)
131
+
132
+ elements = ca.elements
133
+ element_bytes = ca.bytes
134
+ data_bytes = elements * element_bytes
135
+ has_mask = ca.has_mask?
136
+
137
+ # Trailer is present only when there is semantic content: an
138
+ # attribute Hash or a data_class schema. A plain numeric array
139
+ # writes no trailer (both trailer fields zero), keeping the
140
+ # C / FORTRAN reader path a header + raw data.
141
+ trailer = build_trailer(ca)
142
+ trailer_str = trailer.empty? ? nil : encode_trailer(trailer)
143
+
144
+ data_offset = HEADER_BYTES
145
+ mask_offset = has_mask ? data_offset + data_bytes : 0
146
+ mask_bytes = has_mask ? elements : 0
147
+ tail_offset = has_mask ? mask_offset + mask_bytes : data_offset + data_bytes
148
+ trailer_offset = trailer_str ? tail_offset : 0
149
+ trailer_bytes = trailer_str ? trailer_str.bytesize : 0
150
+
151
+ dim = ca.shape
152
+ dim = dim + Array.new(CA_RANK_MAX - dim.size, 0)
153
+
154
+ header = {
155
+ has_mask: has_mask ? 1 : 0,
156
+ has_trailer: trailer_str ? 1 : 0,
157
+ data_type_code: CArray.data_type_code(ca.data_type),
158
+ ndim: ca.ndim,
159
+ shape: dim,
160
+ element_bytes: element_bytes,
161
+ flags: ca.flags,
162
+ elements: elements,
163
+ data_offset: data_offset,
164
+ data_bytes: data_bytes,
165
+ mask_offset: mask_offset,
166
+ mask_bytes: mask_bytes,
167
+ trailer_offset: trailer_offset,
168
+ trailer_bytes: trailer_bytes,
169
+ }
170
+
171
+ @io.write(self.class.pack_header(header, file_endian))
172
+
173
+ data_ca = swap ? ca.swap_bytes : ca
174
+ data_ca.dump_binary(@io)
175
+
176
+ if has_mask
177
+ ca.mask.dump_binary(@io) # int8, endian-neutral
131
178
  end
179
+
180
+ @io.write(trailer_str) if trailer_str
181
+
132
182
  return ca
133
183
  end
134
-
184
+
185
+ # @overload load(data_type: nil)
186
+ # Reads a _CARRAY3 payload from the wrapped IO and returns the
187
+ # reconstructed array, re-wrapping it through its recorded
188
+ # data_class when the trailer carries one.
189
+ # @param opt [Hash] `:data_type` overrides the element type for a
190
+ # bare `CA_FIXLEN` payload.
191
+ # @return [CArray]
192
+ # @raise [RuntimeError] on a bad magic string, corrupt header, or
193
+ # unsupported version.
135
194
  def load (**opt)
136
- if opt[:legacy]
137
- header = Header_Legacy.decode(@io.read(256))
138
- else
139
- header = Header.decode(@io.read(256))
140
- end
141
- if header[:magic_string] != "_CARRAY_"
142
- raise "not a CArray binary data"
143
- end
144
- case header[:endian]
145
- when "_LE_"
146
- endian = CA_LITTLE_ENDIAN
147
- when "_BE_"
148
- endian = CA_BIG_ENDIAN
149
- end
150
- unless CArray.endian == endian
151
- header.swap_bytes!
152
- end
153
- data_type = header[:data_type]
154
- bytes = header[:bytes]
155
- ndim = header[:ndim]
156
- elements = header[:elements]
157
- has_mask = header[:has_mask] != 0 ? true : false
158
- dim = header[:dim][[0, ndim]].to_a
159
- has_attr = header[:has_attr] != 0 ? true : false
160
- has_data_class = header[:has_data_class] != 0 ? true : false
161
- if data_type == 255
162
- data_type = header[:data_type_name].strip.to_sym
163
- end
164
- data_type, bytes = CArray.guess_type_and_bytes(data_type, bytes)
165
- if opt[:data_type] and data_type == CA_FIXLEN
166
- data_type = opt[:data_type]
195
+ buf = @io.read(HEADER_BYTES)
196
+ unless buf && buf.bytesize == HEADER_BYTES
197
+ raise "not a CArray binary data (truncated header)"
198
+ end
199
+ if buf[0, 8] != MAGIC
200
+ raise "not a CArray binary data (bad magic)"
167
201
  end
168
- ca = CArray.new(data_type, dim, :bytes=>bytes)
169
- if data_type == CA_OBJECT
170
- ca.value[] = Marshal.load(@io)
202
+
203
+ # The byte order is self-describing via endian_marker at offset 8:
204
+ # the writer stored 0x01020304 in file order, so the raw bytes are
205
+ # 01 02 03 04 on a big-endian file and 04 03 02 01 on a little-endian
206
+ # one. The leading byte alone decides; anything else is corrupt.
207
+ case buf[8].ord
208
+ when 0x01 then file_endian = CA_BIG_ENDIAN
209
+ when 0x04 then file_endian = CA_LITTLE_ENDIAN
171
210
  else
172
- ca.load_binary(@io)
173
- unless CArray.endian == endian
174
- ca.swap_bytes!
175
- end
211
+ raise "corrupt CArray binary data (endian marker mismatch)"
176
212
  end
177
- if has_mask
213
+ h = self.class.unpack_header(buf, file_endian)
214
+
215
+ if h[:endian_marker] != ENDIAN_MARKER
216
+ raise "corrupt CArray binary data (endian marker mismatch)"
217
+ end
218
+ if h[:version_major] != VERSION_MAJOR
219
+ raise "unsupported CArray binary version #{h[:version_major]}.#{h[:version_minor]}"
220
+ end
221
+ if h[:header_bytes] != HEADER_BYTES
222
+ raise "unsupported CArray binary header size #{h[:header_bytes]}"
223
+ end
224
+ if h[:data_bytes] != h[:elements] * h[:element_bytes]
225
+ raise "corrupt CArray binary data (data_bytes cross-check failed)"
226
+ end
227
+
228
+ swap = (file_endian != CArray.endian)
229
+ data_type = h[:data_type_code]
230
+ ndim = h[:ndim]
231
+ dim = h[:shape][0, ndim]
232
+ bytes = h[:element_bytes]
233
+
234
+ if opt[:data_type] and data_type == CArray.data_type_code(CA_FIXLEN)
235
+ data_type = opt[:data_type]
236
+ end
237
+
238
+ ca = CArray.new(data_type, dim, :bytes => bytes)
239
+ ca.load_binary(@io)
240
+ ca[] = ca.swap_bytes if swap
241
+
242
+ if h[:has_mask] != 0
178
243
  ca.mask = 0
179
244
  ca.mask.load_binary(@io)
180
245
  end
181
- if has_attr
182
- ca.attribute = Marshal.load(@io)
183
- end
184
- if has_data_class
185
- ca.data_class = Kernel.const_get(Marshal.load(@io))
246
+
247
+ if h[:trailer_bytes] > 0
248
+ trailer_raw = @io.read(h[:trailer_bytes])
249
+ trailer = decode_trailer(trailer_raw)
250
+ ca = apply_trailer(ca, trailer)
186
251
  end
252
+
187
253
  return ca
188
254
  end
189
-
255
+
256
+ private
257
+
258
+ # Build the trailer mapping (String-keyed) from the array's
259
+ # attributes and data_class. Returns {} when there is nothing to
260
+ # serialise.
261
+ def build_trailer (ca)
262
+ trailer = {}
263
+ attrs = ca.attrs # frozen, merged along parent chain
264
+ trailer["attrs"] = attrs unless attrs.empty?
265
+ if ca.has_data_class? and ca.data_class
266
+ trailer["data_class"] = build_data_class_schema(ca.data_class)
267
+ end
268
+ trailer
269
+ end
270
+
271
+ # Layer 1 (always) + Layer 2 `name` (when the class is named).
272
+ # ruby_marshal_b64 (full-identity round-trip of anonymous / dynamic
273
+ # classes) is deferred past v1.0 — anonymous classes round-trip via
274
+ # Layer 1 synthesis on load (duck-typed, identity not preserved),
275
+ # matching the "data_class identity out of scope for 3.0" pin.
276
+ def build_data_class_schema (klass)
277
+ members = klass.fields.map do |f|
278
+ if f.bitfield?
279
+ raise ArgumentError,
280
+ "CArray.save cannot serialise data_class #{klass.inspect}: " \
281
+ "member #{f.name.inspect} is a bitfield (the v1.0 Layer 1 " \
282
+ "schema is flat-primitive only); use Marshal.dump(ca)"
283
+ end
284
+ pep = SYMBOL_TO_PEP[f.type]
285
+ unless pep
286
+ raise ArgumentError,
287
+ "CArray.save cannot serialise data_class #{klass.inspect}: " \
288
+ "member #{f.name.inspect} has non-primitive type #{f.type.inspect} " \
289
+ "(nested struct / template / fixlen are not expressible in the " \
290
+ "v1.0 Layer 1 schema); use Marshal.dump(ca)"
291
+ end
292
+ { "name" => f.name, "type" => pep, "offset" => f.offset, "bytes" => f.bytes }
293
+ end
294
+
295
+ schema = {
296
+ "kind" => (klass < CAUnion ? "union" : "struct"),
297
+ "record_bytes" => klass::DATA_SIZE,
298
+ "members" => members,
299
+ }
300
+ schema["name"] = klass.name if klass.name
301
+ schema
302
+ end
303
+
304
+ # Reconstruct attribute Hash + data_class wrapping from a parsed
305
+ # trailer. Returns the (possibly re-wrapped) array.
306
+ def apply_trailer (ca, trailer)
307
+ if trailer.key?("data_class")
308
+ ca = wrap_data_class(ca, trailer["data_class"])
309
+ end
310
+ if trailer.key?("attrs")
311
+ attrs = trailer["attrs"]
312
+ if attrs.is_a?(Hash) and not attrs.empty?
313
+ ca.instance_variable_set(:@attr, attrs)
314
+ end
315
+ end
316
+ ca
317
+ end
318
+
319
+ # Load fall-through: prefer the named Ruby class (const_get), else
320
+ # synthesise an anonymous struct from the Layer 1 members[], else
321
+ # leave the raw CA_FIXLEN array untouched.
322
+ def wrap_data_class (ca, schema)
323
+ name = schema["name"]
324
+ if name
325
+ begin
326
+ klass = Kernel.const_get(name)
327
+ rescue NameError
328
+ klass = nil
329
+ end
330
+ if klass and CArray.data_class?(klass)
331
+ return CARecord.wrap(ca, klass)
332
+ end
333
+ end
334
+
335
+ members = schema["members"]
336
+ if members.is_a?(Array) and not members.empty?
337
+ klass = synthesize_data_class(schema)
338
+ return CARecord.wrap(ca, klass)
339
+ end
340
+
341
+ ca
342
+ end
343
+
344
+ # Build a runtime-defined CArray.struct / .union that reproduces the
345
+ # exact byte layout described by the Layer 1 schema (explicit offsets
346
+ # + total size so trailing padding is preserved).
347
+ def synthesize_data_class (schema)
348
+ members = schema["members"]
349
+ record_bytes = schema["record_bytes"]
350
+ builder = (schema["kind"] == "union") ? CArray.method(:union) : CArray.method(:struct)
351
+ builder.call(:pack => 1, :size => record_bytes) do
352
+ members.each do |m|
353
+ sym = PEP_TO_SYMBOL[m["type"]]
354
+ unless sym
355
+ raise "unknown data_class member type #{m["type"].inspect} in CArray binary data"
356
+ end
357
+ member sym, m["name"], :offset => m["offset"]
358
+ end
359
+ end
360
+ end
361
+
362
+ # YAML flow-style emission. Reads like a JSON one-liner but with
363
+ # native non-finite Float literals (.inf / .nan).
364
+ def encode_trailer (trailer)
365
+ visitor = Psych::Visitors::YAMLTree.create
366
+ visitor << trailer
367
+ ast = visitor.tree
368
+ ast.grep(Psych::Nodes::Mapping).each { |n| n.style = Psych::Nodes::Mapping::FLOW }
369
+ ast.grep(Psych::Nodes::Sequence).each { |n| n.style = Psych::Nodes::Sequence::FLOW }
370
+ # line_width: -1 disables the emitter's line folding so the flow
371
+ # trailer stays a single line regardless of length.
372
+ ast.yaml(nil, :line_width => -1).sub(/\A---\s*/, "").strip.b
373
+ end
374
+
375
+ # safe_load is the symmetric guardrail: it restores the
376
+ # JSON-compatible value set + non-finite Floats and refuses any
377
+ # `!ruby/object:` tag, so a hostile trailer cannot execute code.
378
+ def decode_trailer (raw)
379
+ Psych.safe_load(raw, :permitted_classes => [], :aliases => false) || {}
380
+ end
381
+
382
+ class << self
383
+
384
+ # Pack a header field Hash into a HEADER_BYTES buffer, integers in
385
+ # `file_endian` byte order.
386
+ def pack_header (h, file_endian)
387
+ e = (file_endian == CA_LITTLE_ENDIAN) ? "<" : ">"
388
+ shape = h[:shape]
389
+ body = [
390
+ MAGIC, # a8 0
391
+ ENDIAN_MARKER, # L 8
392
+ VERSION_MAJOR, # C 12
393
+ VERSION_MINOR, # C 13
394
+ HEADER_BYTES, # S 14
395
+ h[:has_mask], # C 16
396
+ h[:has_trailer], # C 17
397
+ h[:data_type_code], # C 18
398
+ h[:ndim], # C 19
399
+ 0, # L 20 reserved0
400
+ *shape, # q16 24
401
+ h[:element_bytes], # L 152
402
+ h[:flags], # l 156
403
+ h[:elements], # Q 160
404
+ h[:data_offset], # Q 168
405
+ h[:data_bytes], # Q 176
406
+ h[:mask_offset], # Q 184
407
+ h[:mask_bytes], # Q 192
408
+ h[:trailer_offset], # Q 200
409
+ h[:trailer_bytes], # Q 208
410
+ 0, # Q 216 data_checksum
411
+ 0, # C 224 checksum_algo
412
+ ].pack("a8L#{e}CCS#{e}CCCCL#{e}q#{e}16L#{e}l#{e}Q#{e}Q#{e}Q#{e}Q#{e}Q#{e}Q#{e}Q#{e}Q#{e}C")
413
+ body.ljust(HEADER_BYTES, "\x00")
414
+ end
415
+
416
+ # Parse a HEADER_BYTES buffer (integers in `file_endian`) into a
417
+ # field Hash. Byte-slice unpack mirrors the C struct offsets.
418
+ def unpack_header (buf, file_endian)
419
+ e = (file_endian == CA_LITTLE_ENDIAN) ? "<" : ">"
420
+ {
421
+ endian_marker: buf[8, 4].unpack1("L#{e}"),
422
+ version_major: buf[12].ord,
423
+ version_minor: buf[13].ord,
424
+ header_bytes: buf[14, 2].unpack1("S#{e}"),
425
+ has_mask: buf[16].ord,
426
+ has_trailer: buf[17].ord,
427
+ data_type_code: buf[18].ord,
428
+ ndim: buf[19].ord,
429
+ shape: buf[24, 128].unpack("q#{e}16"),
430
+ element_bytes: buf[152, 4].unpack1("L#{e}"),
431
+ flags: buf[156, 4].unpack1("l#{e}"),
432
+ elements: buf[160, 8].unpack1("Q#{e}"),
433
+ data_offset: buf[168, 8].unpack1("Q#{e}"),
434
+ data_bytes: buf[176, 8].unpack1("Q#{e}"),
435
+ mask_offset: buf[184, 8].unpack1("Q#{e}"),
436
+ mask_bytes: buf[192, 8].unpack1("Q#{e}"),
437
+ trailer_offset: buf[200, 8].unpack1("Q#{e}"),
438
+ trailer_bytes: buf[208, 8].unpack1("Q#{e}"),
439
+ data_checksum: buf[216, 8].unpack1("Q#{e}"),
440
+ checksum_algo: buf[224].ord,
441
+ }
442
+ end
443
+
444
+ end
445
+
190
446
  end
191
447
 
192
448
  class CArray
193
449
 
194
- def self.save(ca, output, **opt)
450
+ # @overload save(ca, output, **opt)
451
+ # Writes `ca` to `output` in the _CARRAY3 portable format. A
452
+ # String `output` is opened as a binary file; anything else is
453
+ # treated as an IO-like object.
454
+ # @param ca [CArray] array to write.
455
+ # @param output [String, IO] destination path or IO.
456
+ # @param opt [Hash] serializer options (`:endian`).
457
+ # @return [CArray] `ca`.
458
+ def self.save (ca, output, **opt)
195
459
  case output
196
460
  when String
197
461
  open(output, "wb:ASCII-8BIT") { |io|
198
462
  return Serializer.new(io).save(ca, **opt)
199
463
  }
200
464
  else
201
- return Serializer.new(output).save(ca, **opt)
465
+ return Serializer.new(output).save(ca, **opt)
202
466
  end
203
467
  end
204
468
 
469
+ # @overload load(input, **opt)
470
+ # Reads a _CARRAY3 payload from `input`. A String starting with
471
+ # the CArray magic is decoded in place; other Strings are treated
472
+ # as file paths; anything else is used as IO.
473
+ # @param input [String, IO] source path, in-memory payload, or IO.
474
+ # @param opt [Hash] loader options (`:data_type`).
475
+ # @return [CArray]
205
476
  def self.load (input, **opt)
206
477
  case input
207
478
  when String
208
- if input.length >= 256 and input =~ /\A_CARRAY_.{8}_(LE|BE)_/
479
+ if input.bytesize >= Serializer::HEADER_BYTES and
480
+ input.byteslice(0, 8) == Serializer::MAGIC
209
481
  io = StringIO.new(input)
210
482
  return Serializer.new(io).load(**opt)
211
483
  else
@@ -214,31 +486,70 @@ class CArray
214
486
  }
215
487
  end
216
488
  else
217
- return Serializer.new(input).load(**opt)
489
+ return Serializer.new(input).load(**opt)
218
490
  end
219
491
  end
220
492
 
493
+ # @overload dump(ca, **opt)
494
+ # Returns `ca` serialized to a String in the _CARRAY3 format.
495
+ # @param ca [CArray] array to serialize.
496
+ # @param opt [Hash] serializer options (`:endian`).
497
+ # @return [String]
221
498
  def self.dump (ca, **opt)
222
- io = StringIO.new("")
223
- Serializer.new(io).save(ca, **opt)
499
+ io = StringIO.new("".b)
500
+ Serializer.new(io).save(ca, **opt)
224
501
  return io.string
225
502
  end
226
503
 
227
504
  # for Marshal
505
+ #
506
+ # Decision 4: the Marshal path and the portable format are
507
+ # separated. A CA_OBJECT array Marshal-dumps its element VALUEs
508
+ # directly (Ruby-only, no portability promise); every other array
509
+ # reuses the portable format as a numeric container. So
510
+ # Marshal.dump works for object arrays even though CArray.save
511
+ # refuses them.
228
512
 
513
+ # @overload marshal_dump
514
+ # Returns the Marshal payload for `self`. Virtual / wrapped
515
+ # arrays are copied first so the payload always describes an
516
+ # owning array.
517
+ # @return [Array]
229
518
  def marshal_dump ()
230
- if self.class != CArray and self.class != CScalar
231
- return CArray.dump(self.to_ca)
232
- # raise TypeError, "can't dump a virtual or wrapped array."
519
+ target = (self.class != CArray and self.class != CScalar) ? self.copy : self
520
+ if target.data_type == :object
521
+ ["object",
522
+ target.shape,
523
+ target.value.to_a,
524
+ (target.has_mask? ? target.mask.to_a : nil)]
525
+ else
526
+ ["portable", CArray.dump(target)]
233
527
  end
234
- return CArray.dump(self)
235
528
  end
236
529
 
530
+ # @overload marshal_load(data)
531
+ # Reconstitutes `self` from a Marshal payload produced by
532
+ # {#marshal_dump}.
533
+ # @param data [Array] Marshal payload.
534
+ # @return [void]
237
535
  def marshal_load (data)
238
- io = StringIO.new(data)
239
- ca = CArray.load(io)
240
- initialize_copy(ca)
536
+ tag, *rest = data
537
+ case tag
538
+ when "object"
539
+ shape, values, mask = rest
540
+ ca = CArray.object(*shape)
541
+ ca[] = values
542
+ if mask
543
+ ca.mask = 0
544
+ ca.mask[] = mask
545
+ end
546
+ initialize_copy(ca)
547
+ when "portable"
548
+ ca = CArray.load(StringIO.new(rest[0]))
549
+ initialize_copy(ca)
550
+ else
551
+ raise TypeError, "unrecognised CArray Marshal payload"
552
+ end
241
553
  end
242
554
 
243
555
  end
244
-