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.
- checksums.yaml +4 -4
- data/.yardopts +5 -25
- data/CHANGELOG.md +16 -0
- data/LICENSE +1 -1
- data/NEWS.md +3 -0
- data/README.md +128 -44
- data/carray.gemspec +22 -24
- data/ext/ca_array_pool.c +91 -0
- data/ext/ca_axis_descriptor.h +186 -0
- data/ext/ca_axis_dispatch.c +924 -0
- data/ext/ca_axis_group.c +1208 -0
- data/ext/ca_bincmp_dispatch.c +76 -0
- data/ext/ca_bincmp_dispatch.h +85 -0
- data/ext/ca_binop_dispatch.c +125 -0
- data/ext/ca_binop_dispatch.h +159 -0
- data/ext/ca_categorical_iterator.c +1375 -0
- data/ext/ca_compare.c +94 -0
- data/ext/ca_compare.h +26 -0
- data/ext/ca_composite_dispatch.c +414 -0
- data/ext/ca_composite_dispatch.h +116 -0
- data/ext/ca_for_buffer.h +96 -0
- data/ext/ca_for_each_element.h +241 -0
- data/ext/ca_group_iter.c +304 -0
- data/ext/ca_iter_substrate.h +325 -0
- data/ext/ca_kernel_iterator.c +4321 -0
- data/ext/ca_kernel_iterator.h +2603 -0
- data/ext/ca_moncmp_dispatch.c +37 -0
- data/ext/ca_moncmp_dispatch.h +62 -0
- data/ext/ca_monop_dispatch.c +200 -0
- data/ext/ca_monop_dispatch.h +235 -0
- data/ext/ca_obj_array.c +355 -359
- data/ext/ca_obj_bincmp.c +809 -0
- data/ext/ca_obj_binop.c +892 -0
- data/ext/ca_obj_bitarray.c +369 -164
- data/ext/ca_obj_bitfield.c +294 -234
- data/ext/ca_obj_block.c +189 -711
- data/ext/ca_obj_byte_swap.c +766 -0
- data/ext/ca_obj_const_string.c +965 -0
- data/ext/ca_obj_face.c +670 -0
- data/ext/ca_obj_face.h +247 -0
- data/ext/ca_obj_fake.c +228 -100
- data/ext/ca_obj_farray.c +54 -441
- data/ext/ca_obj_field.c +82 -529
- data/ext/ca_obj_fixlen_string.c +306 -0
- data/ext/ca_obj_grid.c +858 -440
- data/ext/ca_obj_meld.c +1034 -0
- data/ext/ca_obj_moncmp.c +569 -0
- data/ext/ca_obj_monop.c +1111 -0
- data/ext/ca_obj_object.c +774 -298
- data/ext/ca_obj_record.c +468 -0
- data/ext/ca_obj_reduce.c +97 -82
- data/ext/ca_obj_refer.c +569 -459
- data/ext/ca_obj_remap.c +475 -0
- data/ext/ca_obj_repeat.c +92 -477
- data/ext/ca_obj_roll.c +616 -0
- data/ext/ca_obj_select.c +344 -296
- data/ext/ca_obj_select_axis.c +1296 -0
- data/ext/ca_obj_shift.c +230 -792
- data/ext/ca_obj_source.c +78 -0
- data/ext/ca_obj_stack.c +1173 -0
- data/ext/ca_obj_stride.c +2501 -0
- data/ext/ca_obj_string.c +268 -0
- data/ext/ca_obj_tile.c +614 -0
- data/ext/ca_obj_time.c +546 -0
- data/ext/ca_obj_timedelta.c +435 -0
- data/ext/ca_obj_transpose.c +62 -516
- data/ext/ca_obj_triop.c +746 -0
- data/ext/ca_obj_unbound_repeat.c +208 -241
- data/ext/ca_obj_window.c +1131 -563
- data/ext/ca_op_byte_swap.c +175 -0
- data/ext/ca_op_ipower.c +319 -0
- data/ext/ca_op_powi.h +88 -0
- data/ext/ca_sort_kernels.h +132 -0
- data/ext/ca_sweep_engine.c +430 -0
- data/ext/ca_sweep_engine.h +157 -0
- data/ext/ca_transform_common.c +228 -0
- data/ext/ca_triop_dispatch.c +55 -0
- data/ext/ca_triop_dispatch.h +62 -0
- data/ext/carray.h +795 -402
- data/ext/carray_access.c +831 -711
- data/ext/carray_attribute.c +98 -330
- data/ext/carray_bincount.c +255 -0
- data/ext/carray_broadcast.c +283 -0
- data/ext/carray_call_cfunc.c +1360 -828
- data/ext/carray_call_cfunc.h +160 -0
- data/ext/carray_cast.c +1212 -301
- data/ext/carray_cast_func.rb +81 -40
- data/ext/carray_class.c +53 -63
- data/ext/carray_config.h +28 -0
- data/ext/carray_conversion.c +350 -346
- data/ext/carray_copy.c +156 -268
- data/ext/carray_core.c +1342 -199
- data/ext/carray_count.c +312 -0
- data/ext/carray_data_type.c +43 -19
- data/ext/carray_element.c +585 -213
- data/ext/carray_factorize.c +2542 -0
- data/ext/carray_generate.c +230 -559
- data/ext/carray_histogram.c +490 -0
- data/ext/carray_hold.c +228 -0
- data/ext/carray_index_classifier.c +1035 -0
- data/ext/carray_index_classifier.h +27 -0
- data/ext/carray_internal.h +120 -0
- data/ext/carray_kernels_bincmp.c +4445 -0
- data/ext/carray_kernels_binop.c +10979 -0
- data/ext/carray_kernels_init.c +36 -0
- data/ext/carray_kernels_map.c +3466 -0
- data/ext/carray_kernels_moncmp.c +2096 -0
- data/ext/carray_kernels_monop.c +18312 -0
- data/ext/carray_kernels_reduce_aggregate.c +25836 -0
- data/ext/carray_kernels_reduce_boolean.c +329 -0
- data/ext/carray_kernels_reduce_cumulative.c +14592 -0
- data/ext/carray_kernels_reduce_extreme.c +16947 -0
- data/ext/carray_kernels_reduce_variance.c +3909 -0
- data/ext/carray_kernels_scan.c +3692 -0
- data/ext/carray_kernels_search.c +32137 -0
- data/ext/carray_kernels_sort.c +10625 -0
- data/ext/carray_kernels_triop.c +1391 -0
- data/ext/carray_lazy.c +567 -0
- data/ext/carray_loop.c +88 -200
- data/ext/carray_mask.c +848 -154
- data/ext/carray_math_kernel.h +120 -0
- data/ext/carray_mathfunc.c +10 -241
- data/ext/carray_median_percentile.c +1257 -0
- data/ext/carray_memory_view.c +1625 -0
- data/ext/carray_operator.c +1526 -318
- data/ext/carray_order.c +664 -1394
- data/ext/carray_partition.c +416 -0
- data/ext/carray_random.c +518 -0
- data/ext/carray_scatter.c +357 -0
- data/ext/carray_slab.c +1219 -0
- data/ext/carray_slab.h +84 -0
- data/ext/carray_sort.c +829 -0
- data/ext/carray_sort_kernel.c +620 -0
- data/ext/carray_struct.c +695 -0
- data/ext/carray_test.c +343 -229
- data/ext/carray_undef.c +34 -17
- data/ext/carray_utils.c +175 -74
- data/ext/extconf.rb +216 -55
- data/ext/mk_call_cfunc.rb +480 -0
- data/ext/mkkernel.rb +8842 -0
- data/ext/ruby_carray.c +202 -101
- data/ext/version.h +4 -14
- data/ext/version.rb +5 -13
- data/lib/carray/arrow_tensor.rb +401 -0
- data/lib/carray/attribute.rb +166 -0
- data/lib/carray/autoload_carray.rb +220 -0
- data/lib/carray/autoload_method_extension.rb +44 -0
- data/lib/carray/axis_group.rb +711 -0
- data/lib/carray/basics.rb +481 -0
- data/lib/carray/bincount_nd.rb +358 -0
- data/lib/carray/block_iterator.rb +604 -0
- data/lib/carray/boolean_reduce.rb +109 -0
- data/lib/carray/categorical.rb +561 -0
- data/lib/carray/categorical_iterator.rb +1062 -0
- data/lib/carray/complex.rb +150 -0
- data/lib/carray/conditional.rb +216 -0
- data/lib/carray/const_string.rb +228 -0
- data/lib/carray/construct.rb +139 -328
- data/lib/carray/core_extensions.rb +240 -0
- data/lib/carray/data_type_extension.rb +233 -0
- data/lib/carray/fixlen_string.rb +95 -0
- data/lib/carray/frame/concat.rb +132 -0
- data/lib/carray/frame/convert.rb +95 -0
- data/lib/carray/frame/csv_parser.rb +211 -0
- data/lib/carray/frame/frame.rb +649 -0
- data/lib/carray/frame/group.rb +186 -0
- data/lib/carray/frame/io.rb +164 -0
- data/lib/carray/frame/join.rb +248 -0
- data/lib/carray/frame/records.rb +99 -0
- data/lib/carray/frame/sort.rb +113 -0
- data/lib/carray/frame/verbs.rb +299 -0
- data/lib/carray/frame.rb +16 -0
- data/lib/carray/histogram.rb +512 -0
- data/lib/carray/inspect.rb +37 -20
- data/lib/carray/iterator.rb +57 -349
- data/lib/carray/lazy.rb +889 -0
- data/lib/carray/mask_gap_fill.rb +200 -0
- data/lib/carray/math.rb +78 -342
- data/lib/carray/meld_reduce.rb +289 -0
- data/lib/carray/methods/align_addr.rb +116 -0
- data/lib/carray/methods/bin.rb +128 -0
- data/lib/carray/methods/bincount.rb +87 -0
- data/lib/carray/methods/bit_string.rb +92 -0
- data/lib/carray/methods/broadcast.rb +63 -0
- data/lib/carray/methods/choose.rb +39 -0
- data/lib/carray/methods/composition.rb +280 -0
- data/lib/carray/methods/gather_nd.rb +206 -0
- data/lib/carray/methods/index.rb +39 -0
- data/lib/carray/methods/insert_block.rb +99 -0
- data/lib/carray/methods/is_in.rb +141 -0
- data/lib/carray/methods/join.rb +90 -0
- data/lib/carray/methods/locate_addr.rb +47 -0
- data/lib/carray/methods/mask_duplicates.rb +41 -0
- data/lib/carray/methods/meshgrid.rb +91 -0
- data/lib/carray/methods/mode.rb +126 -0
- data/lib/carray/methods/nunique.rb +46 -0
- data/lib/carray/methods/resize.rb +56 -0
- data/lib/carray/methods/snap.rb +156 -0
- data/lib/carray/methods/string_format.rb +57 -0
- data/lib/carray/methods/unique.rb +47 -0
- data/lib/carray/methods/value_counts.rb +71 -0
- data/lib/carray/mkmf.rb +124 -101
- data/lib/carray/runtime.rb +108 -0
- data/lib/carray/serialize.rb +478 -167
- data/lib/carray/slab_iterator.rb +292 -0
- data/lib/carray/stack.rb +291 -0
- data/lib/carray/string.rb +56 -180
- data/lib/carray/string_operation_extension.rb +289 -0
- data/lib/carray/struct.rb +335 -323
- data/lib/carray/struct_builder.rb +697 -0
- data/lib/carray/table.rb +41 -2
- data/lib/carray/time.rb +2255 -38
- data/lib/carray/window_iterator.rb +655 -0
- data/lib/carray.rb +55 -57
- metadata +163 -130
- data/Rakefile +0 -51
- data/TODO.md +0 -18
- data/ext/ca_iter_block.c +0 -257
- data/ext/ca_iter_dimension.c +0 -299
- data/ext/ca_iter_window.c +0 -214
- data/ext/ca_obj_mapping.c +0 -644
- data/ext/carray_iterator.c +0 -641
- data/ext/carray_math.rb +0 -850
- data/ext/carray_numeric.c +0 -259
- data/ext/carray_sort_addr.c +0 -254
- data/ext/carray_stat.c +0 -2100
- data/ext/carray_stat_proc.rb +0 -1999
- data/ext/mkmath.rb +0 -741
- data/ext/ruby_ccomplex.c +0 -509
- data/ext/ruby_float_func.c +0 -86
- data/lib/carray/array.rb +0 -8
- data/lib/carray/autoload/autoload_base.rb +0 -19
- data/lib/carray/autoload/autoload_gem_cairo.rb +0 -9
- data/lib/carray/autoload/autoload_gem_ffi.rb +0 -9
- data/lib/carray/autoload/autoload_gem_gnuplot.rb +0 -2
- data/lib/carray/autoload/autoload_gem_io_csv.rb +0 -14
- data/lib/carray/autoload/autoload_gem_io_pg.rb +0 -6
- data/lib/carray/autoload/autoload_gem_io_sqlite3.rb +0 -12
- data/lib/carray/autoload/autoload_gem_narray.rb +0 -10
- data/lib/carray/autoload/autoload_gem_numo_narray.rb +0 -15
- data/lib/carray/autoload/autoload_gem_opencv.rb +0 -16
- data/lib/carray/autoload/autoload_gem_random.rb +0 -8
- data/lib/carray/autoload/autoload_gem_rmagick.rb +0 -23
- data/lib/carray/autoload/autoload_gem_zimg.rb +0 -3
- data/lib/carray/autoload/autoload_io_imagemagick.rb +0 -6
- data/lib/carray/autoload/autoload_math_histogram.rb +0 -5
- data/lib/carray/autoload/autoload_math_recurrence.rb +0 -6
- data/lib/carray/autoload/autoload_object_iterator.rb +0 -1
- data/lib/carray/autoload/autoload_object_link.rb +0 -1
- data/lib/carray/autoload/autoload_object_pack.rb +0 -2
- data/lib/carray/autoload.rb +0 -141
- data/lib/carray/basic.rb +0 -191
- data/lib/carray/broadcast.rb +0 -101
- data/lib/carray/compose.rb +0 -315
- data/lib/carray/convert.rb +0 -115
- data/lib/carray/info.rb +0 -110
- data/lib/carray/io/imagemagick.rb +0 -235
- data/lib/carray/mask.rb +0 -102
- data/lib/carray/math/histogram.rb +0 -177
- data/lib/carray/math/recurrence.rb +0 -93
- data/lib/carray/object/ca_obj_iterator.rb +0 -50
- data/lib/carray/object/ca_obj_link.rb +0 -50
- data/lib/carray/object/ca_obj_pack.rb +0 -99
- data/lib/carray/obsolete.rb +0 -256
- data/lib/carray/ordering.rb +0 -181
- data/lib/carray/testing.rb +0 -51
- data/lib/carray/transform.rb +0 -109
- data/misc/Methods.ja.md +0 -182
- data/misc/NOTE +0 -51
- data/spec/Classes/CABitfield_spec.rb +0 -58
- data/spec/Classes/CABlockIterator_spec.rb +0 -114
- data/spec/Classes/CABlock_spec.rb +0 -205
- data/spec/Classes/CAField_spec.rb +0 -39
- data/spec/Classes/CAGrid_spec.rb +0 -75
- data/spec/Classes/CAMap_spec.rb +0 -0
- data/spec/Classes/CAMapping_spec.rb +0 -105
- data/spec/Classes/CAObject_attribute_spec.rb +0 -33
- data/spec/Classes/CAObject_spec.rb +0 -33
- data/spec/Classes/CARefer_spec.rb +0 -93
- data/spec/Classes/CARepeat_spec.rb +0 -65
- data/spec/Classes/CASelect_spec.rb +0 -22
- data/spec/Classes/CAShift_spec.rb +0 -16
- data/spec/Classes/CAStruct_spec.rb +0 -71
- data/spec/Classes/CATranspose_spec.rb +0 -60
- data/spec/Classes/CAUnboudRepeat_spec.rb +0 -102
- data/spec/Classes/CAWindow_spec.rb +0 -54
- data/spec/Classes/CAWrap_spec.rb +0 -8
- data/spec/Classes/CArray_spec.rb +0 -184
- data/spec/Classes/CScalar_spec.rb +0 -55
- data/spec/Classes/ex1.rb +0 -46
- data/spec/Features/feature_130_spec.rb +0 -19
- data/spec/Features/feature_attributes_spec.rb +0 -280
- data/spec/Features/feature_boolean_spec.rb +0 -98
- data/spec/Features/feature_broadcast.rb +0 -116
- data/spec/Features/feature_cast_function.rb +0 -19
- data/spec/Features/feature_cast_spec.rb +0 -33
- data/spec/Features/feature_class_spec.rb +0 -84
- data/spec/Features/feature_complex_spec.rb +0 -42
- data/spec/Features/feature_composite_spec.rb +0 -124
- data/spec/Features/feature_convert_spec.rb +0 -46
- data/spec/Features/feature_copy_spec.rb +0 -123
- data/spec/Features/feature_creation_spec.rb +0 -84
- data/spec/Features/feature_element_spec.rb +0 -144
- data/spec/Features/feature_extream_spec.rb +0 -54
- data/spec/Features/feature_generate_spec.rb +0 -74
- data/spec/Features/feature_index_spec.rb +0 -69
- data/spec/Features/feature_mask_spec.rb +0 -580
- data/spec/Features/feature_math_spec.rb +0 -97
- data/spec/Features/feature_order_spec.rb +0 -146
- data/spec/Features/feature_ref_store_spec.rb +0 -209
- data/spec/Features/feature_serialization_spec.rb +0 -125
- data/spec/Features/feature_stat_spec.rb +0 -397
- data/spec/Features/feature_virtual_spec.rb +0 -48
- data/spec/Features/method_eq_spec.rb +0 -81
- data/spec/Features/method_is_nan_spec.rb +0 -12
- data/spec/Features/method_map_spec.rb +0 -54
- data/spec/Features/method_max_with.rb +0 -20
- data/spec/Features/method_min_with.rb +0 -19
- data/spec/Features/method_ne_spec.rb +0 -18
- data/spec/Features/method_project_spec.rb +0 -188
- data/spec/Features/method_ref_spec.rb +0 -27
- data/spec/Features/method_round_spec.rb +0 -11
- data/spec/Features/method_s_linspace_spec.rb +0 -48
- data/spec/Features/method_s_span_spec.rb +0 -14
- data/spec/Features/method_seq_spec.rb +0 -47
- data/spec/Features/method_sort_with.rb +0 -43
- data/spec/Features/method_sorted_with.rb +0 -29
- data/spec/Features/method_span_spec.rb +0 -42
- data/spec/Features/method_wrap_readonly_spec.rb +0 -43
- data/spec/UnitTest/test_CAVirtual.rb +0 -214
- data/spec/spec_all.rb +0 -10
- data/utils/ca_ase.rb +0 -21
- data/utils/ca_methods.rb +0 -15
- data/utils/cast_checker.rb +0 -30
- data/utils/convert_test.rb +0 -73
- data/utils/extract_yard.rb +0 -22
- data/utils/guess_shape.rb +0 -76
- data/utils/monkey_patch_methods.rb +0 -62
- data/utils/remove_resource_fork.sh +0 -5
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Apache Arrow "tensor IPC" reader / writer for CArray. [experimental]
|
|
2
|
+
#
|
|
3
|
+
# This surface is experimental: the method names, the module namespace, and
|
|
4
|
+
# the packaging (it may later be extracted into a standalone, array-library-
|
|
5
|
+
# neutral gem) are not yet frozen and may change in a future release.
|
|
6
|
+
#
|
|
7
|
+
# This is an interoperability layer, not an archival format: it exchanges
|
|
8
|
+
# a dense numeric array with the Arrow ecosystem (pyarrow.ipc.write_tensor /
|
|
9
|
+
# read_tensor and equivalents) using Arrow's encapsulated tensor message.
|
|
10
|
+
# For faithful, reproducible persistence of a CArray -- mask, attributes,
|
|
11
|
+
# data_class and all -- use CArray.save / CArray.load (the _CARRAY3 format).
|
|
12
|
+
#
|
|
13
|
+
# The on-disk shape is: a 0xFFFFFFFF continuation marker, an int32 metadata
|
|
14
|
+
# length, a FlatBuffers Message whose header is a Tensor (element type, shape,
|
|
15
|
+
# byte strides, and a Buffer descriptor), padding to an 8-byte boundary, then
|
|
16
|
+
# the raw contiguous element bytes. Only the numeric types Arrow tensors
|
|
17
|
+
# admit are supported (int8..int64, uint8..uint64, float32, float64); boolean,
|
|
18
|
+
# complex, fixlen and object arrays have no Arrow tensor representation and are
|
|
19
|
+
# rejected. A masked CArray is rejected too, mirroring the MemoryView policy:
|
|
20
|
+
# Arrow tensors carry no validity concept, so the caller must be explicit
|
|
21
|
+
# (ca.value to drop the mask, ca.unmask_copy(fill) to bake it in, or persist
|
|
22
|
+
# the mask separately through CArray.save).
|
|
23
|
+
|
|
24
|
+
require "stringio"
|
|
25
|
+
|
|
26
|
+
class CArray
|
|
27
|
+
# Reader / writer for Arrow's encapsulated tensor IPC message, for
|
|
28
|
+
# exchanging a dense numeric array with the Arrow ecosystem
|
|
29
|
+
# (`pyarrow.ipc.write_tensor` / `read_tensor` and equivalents).
|
|
30
|
+
#
|
|
31
|
+
# Experimental: the method names, this namespace and the packaging are not
|
|
32
|
+
# frozen, and the layer may later move to a standalone, array-library-neutral
|
|
33
|
+
# gem. It is an interoperability format, not an archival one — an Arrow
|
|
34
|
+
# tensor carries no mask, attributes or data_class, so `CArray.save` /
|
|
35
|
+
# `CArray.load` remain the way to persist a CArray faithfully.
|
|
36
|
+
#
|
|
37
|
+
# Only the numeric types an Arrow tensor admits are supported (int8..int64,
|
|
38
|
+
# uint8..uint64, float32, float64). Boolean, complex, fixlen and object
|
|
39
|
+
# arrays are rejected, as is a masked array — Arrow tensors have no validity
|
|
40
|
+
# concept, so the caller must be explicit (`ca.value` to drop the mask,
|
|
41
|
+
# `ca.unmask_copy(fill)` to bake it in, or save the mask separately).
|
|
42
|
+
module ArrowTensor
|
|
43
|
+
|
|
44
|
+
# CArray data_type symbol -> Arrow element-type descriptor.
|
|
45
|
+
# [:float, precision] precision: 1 = single, 2 = double
|
|
46
|
+
# [:int, bit_width, signed?]
|
|
47
|
+
DTYPE_TO_ARROW = {
|
|
48
|
+
float32: [:float, 1], float64: [:float, 2],
|
|
49
|
+
int8: [:int, 8, true], int16: [:int, 16, true],
|
|
50
|
+
int32: [:int, 32, true], int64: [:int, 64, true],
|
|
51
|
+
uint8: [:int, 8, false], uint16:[:int, 16, false],
|
|
52
|
+
uint32:[:int, 32, false], uint64:[:int, 64, false],
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
module_function
|
|
56
|
+
|
|
57
|
+
# Write +ca+ to +io+ as an Arrow tensor IPC message.
|
|
58
|
+
def write (ca, io)
|
|
59
|
+
if ca.has_mask?
|
|
60
|
+
raise ArgumentError,
|
|
61
|
+
"cannot write a masked CArray as an Arrow tensor " \
|
|
62
|
+
"(Arrow tensors have no validity concept); pass ca.value to " \
|
|
63
|
+
"drop the mask, ca.unmask_copy(fill) to fill it, or use " \
|
|
64
|
+
"CArray.save for a mask-preserving round-trip"
|
|
65
|
+
end
|
|
66
|
+
arrow = DTYPE_TO_ARROW[ca.data_type]
|
|
67
|
+
unless arrow
|
|
68
|
+
raise ArgumentError,
|
|
69
|
+
"cannot write a #{ca.data_type} CArray as an Arrow tensor " \
|
|
70
|
+
"(Arrow tensors are numeric only: int8..int64, uint8..uint64, " \
|
|
71
|
+
"float32, float64)"
|
|
72
|
+
end
|
|
73
|
+
raise ArgumentError, "cannot write a 0-dimensional array" if ca.ndim < 1
|
|
74
|
+
|
|
75
|
+
shape = ca.shape
|
|
76
|
+
elt = ca.bytes
|
|
77
|
+
strides = row_major_strides(shape, elt)
|
|
78
|
+
|
|
79
|
+
body = StringIO.new("".b)
|
|
80
|
+
ca.dump_binary(body) # raw contiguous element bytes, row-major
|
|
81
|
+
body = body.string
|
|
82
|
+
|
|
83
|
+
meta = build_metadata(arrow, shape, strides, body.bytesize)
|
|
84
|
+
pad = (8 - ((8 + meta.bytesize) % 8)) % 8
|
|
85
|
+
meta += "\0".b * pad
|
|
86
|
+
|
|
87
|
+
io.write([0xFFFFFFFF].pack("V"))
|
|
88
|
+
io.write([meta.bytesize].pack("l<"))
|
|
89
|
+
io.write(meta)
|
|
90
|
+
io.write(body)
|
|
91
|
+
ca
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Read an Arrow tensor IPC message from +io+ and return a new CArray.
|
|
95
|
+
def read (io)
|
|
96
|
+
b = io.respond_to?(:read) ? io.read : io.to_s
|
|
97
|
+
b = b.b
|
|
98
|
+
meta = parse_metadata(b)
|
|
99
|
+
type = meta[:data_type]
|
|
100
|
+
unless CArray.respond_to?(:data_type_code) && DTYPE_TO_ARROW.key?(type)
|
|
101
|
+
raise ArgumentError,
|
|
102
|
+
"Arrow tensor element type #{type.inspect} has no CArray " \
|
|
103
|
+
"counterpart"
|
|
104
|
+
end
|
|
105
|
+
build_carray(type, meta[:shape], meta[:strides], meta[:body])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# ---- helpers ----------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def row_major_strides (shape, elt)
|
|
111
|
+
s = Array.new(shape.length)
|
|
112
|
+
acc = elt
|
|
113
|
+
(shape.length - 1).downto(0) do |i|
|
|
114
|
+
s[i] = acc
|
|
115
|
+
acc *= shape[i]
|
|
116
|
+
end
|
|
117
|
+
s
|
|
118
|
+
end
|
|
119
|
+
private_class_method :row_major_strides
|
|
120
|
+
|
|
121
|
+
def build_carray (type, shape, strides, body)
|
|
122
|
+
elt = CArray.new(type, [1]).bytes
|
|
123
|
+
rmaj = row_major_strides(shape, elt)
|
|
124
|
+
if strides == rmaj
|
|
125
|
+
ca = CArray.new(type, shape)
|
|
126
|
+
ca.load_binary(StringIO.new(body))
|
|
127
|
+
return ca
|
|
128
|
+
end
|
|
129
|
+
cmaj = row_major_strides(shape.reverse, elt).reverse
|
|
130
|
+
if strides == cmaj
|
|
131
|
+
t = CArray.new(type, shape.reverse)
|
|
132
|
+
t.load_binary(StringIO.new(body))
|
|
133
|
+
return t.transpose(*(0...shape.length).to_a.reverse).to_ca
|
|
134
|
+
end
|
|
135
|
+
# General strided layout (rare): gather element by element.
|
|
136
|
+
gather_strided(type, shape, strides, body)
|
|
137
|
+
end
|
|
138
|
+
private_class_method :build_carray
|
|
139
|
+
|
|
140
|
+
def gather_strided (type, shape, strides, body)
|
|
141
|
+
elt = CArray.new(type, [1]).bytes
|
|
142
|
+
code = UNPACK[type]
|
|
143
|
+
ca = CArray.new(type, shape)
|
|
144
|
+
elements = shape.inject(1, :*)
|
|
145
|
+
idx = Array.new(shape.length, 0)
|
|
146
|
+
elements.times do
|
|
147
|
+
off = 0
|
|
148
|
+
idx.each_with_index { |v, k| off += v * strides[k] }
|
|
149
|
+
ca[*idx] = body[off, elt].unpack1(code)
|
|
150
|
+
k = shape.length - 1
|
|
151
|
+
while k >= 0
|
|
152
|
+
idx[k] += 1
|
|
153
|
+
break if idx[k] < shape[k]
|
|
154
|
+
idx[k] = 0
|
|
155
|
+
k -= 1
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
ca
|
|
159
|
+
end
|
|
160
|
+
private_class_method :gather_strided
|
|
161
|
+
|
|
162
|
+
# @!visibility private
|
|
163
|
+
UNPACK = {
|
|
164
|
+
float32:"e", float64:"E",
|
|
165
|
+
int8:"c", int16:"s<", int32:"l<", int64:"q<",
|
|
166
|
+
uint8:"C", uint16:"v", uint32:"V", uint64:"Q<",
|
|
167
|
+
}.freeze
|
|
168
|
+
|
|
169
|
+
# ---- FlatBuffers Message encoder (header = Tensor) --------------------
|
|
170
|
+
|
|
171
|
+
# Minimal back-to-front FlatBuffers builder, specialised to the fields the
|
|
172
|
+
# Tensor message uses (scalars, tables, vectors, one inline struct).
|
|
173
|
+
#
|
|
174
|
+
# @!visibility private
|
|
175
|
+
class Builder
|
|
176
|
+
def initialize
|
|
177
|
+
@buf = "".b
|
|
178
|
+
@minalign = 1
|
|
179
|
+
@vt = nil
|
|
180
|
+
@obj_start = nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# @!visibility private
|
|
184
|
+
def offset; @buf.bytesize; end
|
|
185
|
+
|
|
186
|
+
# @!visibility private
|
|
187
|
+
def prep (size, additional)
|
|
188
|
+
@minalign = size if size > @minalign
|
|
189
|
+
align = ((~(@buf.bytesize + additional)) + 1) & (size - 1)
|
|
190
|
+
@buf.prepend("\0".b * align) if align > 0
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @!visibility private
|
|
194
|
+
def u8 (x); @buf.prepend([x].pack("C")); end
|
|
195
|
+
# @!visibility private
|
|
196
|
+
def i16 (x); @buf.prepend([x].pack("s<")); end
|
|
197
|
+
# @!visibility private
|
|
198
|
+
def u16 (x); @buf.prepend([x].pack("v")); end
|
|
199
|
+
# @!visibility private
|
|
200
|
+
def u32 (x); @buf.prepend([x].pack("V")); end
|
|
201
|
+
# @!visibility private
|
|
202
|
+
def i32 (x); @buf.prepend([x].pack("l<")); end
|
|
203
|
+
# @!visibility private
|
|
204
|
+
def i64 (x); @buf.prepend([x].pack("q<")); end
|
|
205
|
+
|
|
206
|
+
# @!visibility private
|
|
207
|
+
def uoffset (off)
|
|
208
|
+
prep(4, 0)
|
|
209
|
+
u32(offset() - off + 4)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# @!visibility private
|
|
213
|
+
def slot (v); @vt[v] = offset(); end
|
|
214
|
+
|
|
215
|
+
# @!visibility private
|
|
216
|
+
def slot_i16 (v, x); prep(2,0); i16(x); slot(v); end
|
|
217
|
+
# @!visibility private
|
|
218
|
+
def slot_u8 (v, x); prep(1,0); u8(x); slot(v); end
|
|
219
|
+
# @!visibility private
|
|
220
|
+
def slot_i32 (v, x); prep(4,0); i32(x); slot(v); end
|
|
221
|
+
# @!visibility private
|
|
222
|
+
def slot_i64 (v, x); prep(8,0); i64(x); slot(v); end
|
|
223
|
+
# @!visibility private
|
|
224
|
+
def slot_bool(v, x); prep(1,0); u8(x ? 1 : 0); slot(v); end
|
|
225
|
+
# @!visibility private
|
|
226
|
+
def slot_uoffset (v, off); uoffset(off); slot(v); end
|
|
227
|
+
|
|
228
|
+
# @!visibility private
|
|
229
|
+
def start_object (n); @vt = Array.new(n, 0); @obj_start = offset(); end
|
|
230
|
+
|
|
231
|
+
# @!visibility private
|
|
232
|
+
def end_object
|
|
233
|
+
prep(4, 0)
|
|
234
|
+
i32(0) # soffset placeholder
|
|
235
|
+
obj = offset()
|
|
236
|
+
n = @vt.length
|
|
237
|
+
n -= 1 while n > 0 && @vt[n-1] == 0
|
|
238
|
+
(n-1).downto(0) do |i|
|
|
239
|
+
u16(@vt[i] == 0 ? 0 : (obj - @vt[i]))
|
|
240
|
+
end
|
|
241
|
+
u16(obj - @obj_start) # referenced table size
|
|
242
|
+
u16((n + 2) * 2) # vtable size
|
|
243
|
+
soff = offset() - obj
|
|
244
|
+
@buf[@buf.bytesize - obj, 4] = [soff].pack("l<")
|
|
245
|
+
obj
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# @!visibility private
|
|
249
|
+
def start_vector (elem, count, align); prep(4, elem*count); prep(align, elem*count); end
|
|
250
|
+
# @!visibility private
|
|
251
|
+
def end_vector (count); u32(count); offset(); end
|
|
252
|
+
|
|
253
|
+
# @!visibility private
|
|
254
|
+
def finish (root)
|
|
255
|
+
prep(@minalign, 4)
|
|
256
|
+
uoffset(root)
|
|
257
|
+
@buf
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def build_metadata (arrow, shape, strides, body_len)
|
|
262
|
+
fb = Builder.new
|
|
263
|
+
tag = arrow[0] == :float ? 3 : 2
|
|
264
|
+
|
|
265
|
+
type_off =
|
|
266
|
+
if arrow[0] == :float
|
|
267
|
+
fb.start_object(1)
|
|
268
|
+
fb.slot_i16(0, arrow[1]) # precision
|
|
269
|
+
fb.end_object
|
|
270
|
+
else
|
|
271
|
+
fb.start_object(2)
|
|
272
|
+
fb.slot_i32(0, arrow[1]) # bitWidth
|
|
273
|
+
fb.slot_bool(1, arrow[2]) # is_signed
|
|
274
|
+
fb.end_object
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
dim_offs = shape.map do |sz|
|
|
278
|
+
fb.start_object(2)
|
|
279
|
+
fb.slot_i64(0, sz) # TensorDim.size (name omitted)
|
|
280
|
+
fb.end_object
|
|
281
|
+
end
|
|
282
|
+
fb.start_vector(4, dim_offs.length, 4)
|
|
283
|
+
dim_offs.reverse_each { |o| fb.uoffset(o) }
|
|
284
|
+
shape_vec = fb.end_vector(dim_offs.length)
|
|
285
|
+
|
|
286
|
+
fb.start_vector(8, strides.length, 8)
|
|
287
|
+
strides.reverse_each { |s| fb.i64(s) }
|
|
288
|
+
strides_vec = fb.end_vector(strides.length)
|
|
289
|
+
|
|
290
|
+
fb.start_object(5) # Tensor: type_type,type,shape,strides,data
|
|
291
|
+
fb.prep(8, 16) # data: inline Buffer struct
|
|
292
|
+
fb.i64(body_len) # length
|
|
293
|
+
fb.i64(0) # offset
|
|
294
|
+
fb.slot(4)
|
|
295
|
+
fb.slot_uoffset(3, strides_vec)
|
|
296
|
+
fb.slot_uoffset(2, shape_vec)
|
|
297
|
+
fb.slot_uoffset(1, type_off)
|
|
298
|
+
fb.slot_u8(0, tag) # type_type
|
|
299
|
+
tensor_off = fb.end_object
|
|
300
|
+
|
|
301
|
+
fb.start_object(5) # Message: version,header_type,header,bodyLength,custom
|
|
302
|
+
fb.slot_i64(3, body_len)
|
|
303
|
+
fb.slot_uoffset(2, tensor_off)
|
|
304
|
+
fb.slot_u8(1, 4) # header_type = Tensor
|
|
305
|
+
fb.slot_i16(0, 4) # metadata version V5
|
|
306
|
+
msg_off = fb.end_object
|
|
307
|
+
|
|
308
|
+
fb.finish(msg_off)
|
|
309
|
+
end
|
|
310
|
+
private_class_method :build_metadata
|
|
311
|
+
|
|
312
|
+
# ---- FlatBuffers Message decoder --------------------------------------
|
|
313
|
+
|
|
314
|
+
def parse_metadata (b)
|
|
315
|
+
u16 = ->(o){ b[o,2].unpack1("v") }
|
|
316
|
+
i32 = ->(o){ b[o,4].unpack1("l<") }
|
|
317
|
+
u32 = ->(o){ b[o,4].unpack1("V") }
|
|
318
|
+
i64 = ->(o){ b[o,8].unpack1("q<") }
|
|
319
|
+
field = ->(t,f){ vt = t - i32.(t); vts = u16.(vt); slot = 4 + f*2
|
|
320
|
+
next 0 if slot >= vts; vo = u16.(vt+slot); vo==0 ? 0 : t+vo }
|
|
321
|
+
deref = ->(pos){ pos + u32.(pos) }
|
|
322
|
+
|
|
323
|
+
raise ArgumentError, "not an Arrow IPC message" unless u32.(0) == 0xffffffff
|
|
324
|
+
meta_len = i32.(4)
|
|
325
|
+
fb = 8
|
|
326
|
+
body_off = 8 + meta_len
|
|
327
|
+
|
|
328
|
+
msg = fb + i32.(fb)
|
|
329
|
+
htag = (p = field.(msg,1); p == 0 ? 0 : b.getbyte(p))
|
|
330
|
+
raise ArgumentError, "not a tensor message" unless htag == 4
|
|
331
|
+
hpos = deref.(field.(msg,2))
|
|
332
|
+
|
|
333
|
+
type_tag = b.getbyte(field.(hpos,0))
|
|
334
|
+
type_tbl = deref.(field.(hpos,1))
|
|
335
|
+
shp_vec = deref.(field.(hpos,2)); n = u32.(shp_vec)
|
|
336
|
+
shape = (0...n).map { |i| dp = deref.(shp_vec + 4 + i*4); i64.(field.(dp,0)) }
|
|
337
|
+
sv_slot = field.(hpos,3)
|
|
338
|
+
strides =
|
|
339
|
+
if sv_slot == 0
|
|
340
|
+
row_major_strides(shape, CArray.new(arrow_symbol(type_tag, type_tbl, u16, i32, b), [1]).bytes)
|
|
341
|
+
else
|
|
342
|
+
sv = deref.(sv_slot); m = u32.(sv); (0...m).map { |i| i64.(sv + 4 + i*8) }
|
|
343
|
+
end
|
|
344
|
+
dpos = field.(hpos,4)
|
|
345
|
+
data_off = i64.(dpos); data_len = i64.(dpos+8)
|
|
346
|
+
|
|
347
|
+
{ data_type: arrow_symbol(type_tag, type_tbl, u16, i32, b),
|
|
348
|
+
shape: shape, strides: strides,
|
|
349
|
+
body: b[body_off + data_off, data_len] }
|
|
350
|
+
end
|
|
351
|
+
private_class_method :parse_metadata
|
|
352
|
+
|
|
353
|
+
def arrow_symbol (tag, tbl, u16, i32, b)
|
|
354
|
+
case tag
|
|
355
|
+
when 3
|
|
356
|
+
{ 0=>:float16, 1=>:float32, 2=>:float64 }[u16.(tbl_field(tbl, 0, i32, u16))] ||
|
|
357
|
+
(raise ArgumentError, "unsupported Arrow float precision")
|
|
358
|
+
when 2
|
|
359
|
+
bw = i32.(tbl_field(tbl, 0, i32, u16))
|
|
360
|
+
sf = tbl_field(tbl, 1, i32, u16)
|
|
361
|
+
signed = sf == 0 ? false : (b.getbyte(sf) != 0) # is_signed FlatBuffers default = false
|
|
362
|
+
(signed ? "int#{bw}" : "uint#{bw}").to_sym
|
|
363
|
+
else
|
|
364
|
+
raise ArgumentError, "unsupported Arrow tensor element type (tag=#{tag})"
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
private_class_method :arrow_symbol
|
|
368
|
+
|
|
369
|
+
def tbl_field (t, f, i32, u16)
|
|
370
|
+
vt = t - i32.(t); vts = u16.(vt); slot = 4 + f*2
|
|
371
|
+
return 0 if slot >= vts
|
|
372
|
+
vo = u16.(vt + slot); vo == 0 ? 0 : t + vo
|
|
373
|
+
end
|
|
374
|
+
private_class_method :tbl_field
|
|
375
|
+
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Reads an Arrow tensor IPC file and returns it as a new CArray.
|
|
379
|
+
#
|
|
380
|
+
# Experimental, and the name is provisional.
|
|
381
|
+
#
|
|
382
|
+
# @param filename [String] path to the message.
|
|
383
|
+
# @return [CArray]
|
|
384
|
+
# @see ArrowTensor
|
|
385
|
+
def self.load_arrow_tensor (filename)
|
|
386
|
+
File.open(filename, "rb") { |io| ArrowTensor.read(io) }
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Writes `self` to `filename` as an Arrow tensor IPC message.
|
|
390
|
+
#
|
|
391
|
+
# Experimental, and the name is provisional. Rejects a masked or
|
|
392
|
+
# non-numeric array; see {ArrowTensor} for the type policy.
|
|
393
|
+
#
|
|
394
|
+
# @param filename [String] path to write.
|
|
395
|
+
# @return [self]
|
|
396
|
+
# @see ArrowTensor
|
|
397
|
+
def save_arrow_tensor (filename)
|
|
398
|
+
File.open(filename, "wb") { |io| ArrowTensor.write(self, io) }
|
|
399
|
+
self
|
|
400
|
+
end
|
|
401
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# User-defined arbitrary key/value metadata attached to a CArray
|
|
2
|
+
# instance.
|
|
3
|
+
#
|
|
4
|
+
# Surface:
|
|
5
|
+
# ca.attr(:unit) # => getter; nil if absent
|
|
6
|
+
# ca.set_attr(:unit, "m/s") # => setter; writes to self's @attr
|
|
7
|
+
# ca.attrs # => frozen shallow clone of the full Hash
|
|
8
|
+
# ca.has_attr? # => any attribute present?
|
|
9
|
+
# ca.has_attr?(:unit) # => specific key present?
|
|
10
|
+
#
|
|
11
|
+
# Key: accepts Symbol or String on input; stored as String.
|
|
12
|
+
# Value: JSON-compatible plus non-finite Floats (String / Numeric,
|
|
13
|
+
# including Infinity / NaN / -Infinity / true / false / nil /
|
|
14
|
+
# Array / Hash<String|Symbol, ...>). Symbol values are
|
|
15
|
+
# coerced to String on store. Disallowed types raise
|
|
16
|
+
# TypeError on set. Non-finite Floats are accepted so
|
|
17
|
+
# imported metadata (e.g. a NetCDF _FillValue of Infinity)
|
|
18
|
+
# is not rejected; the serialize trailer is YAML, whose
|
|
19
|
+
# .inf / .nan literals round-trip them (serialize.rb).
|
|
20
|
+
#
|
|
21
|
+
# View semantics (per-key fallback):
|
|
22
|
+
# Getter side (attr / has_attr?) walks the parent chain
|
|
23
|
+
# *per key*: if the receiver's @attr has the key, return it;
|
|
24
|
+
# otherwise walk up. Each view shadows its parents one key at
|
|
25
|
+
# a time, so writing :slice on a view does not hide :unit
|
|
26
|
+
# inherited from the parent entity.
|
|
27
|
+
# `attrs` returns the effective merged Hash (deeper writes
|
|
28
|
+
# override shallower ones), frozen.
|
|
29
|
+
# Setter side always writes to self's @attr.
|
|
30
|
+
|
|
31
|
+
class CArray
|
|
32
|
+
|
|
33
|
+
# @!group Attributes
|
|
34
|
+
|
|
35
|
+
# @overload attr(key)
|
|
36
|
+
# Returns the value of the attribute `key`, or `nil` when the key
|
|
37
|
+
# is absent. Walks the parent chain per key: the deepest view
|
|
38
|
+
# that has an entry for `key` wins.
|
|
39
|
+
# @param key [Symbol, String] attribute key.
|
|
40
|
+
# @return [Object, nil]
|
|
41
|
+
def attr (key)
|
|
42
|
+
k = attr_normalize_key(key)
|
|
43
|
+
attr_each_chain do |h|
|
|
44
|
+
return h[k] if h.key?(k)
|
|
45
|
+
end
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @overload set_attr(key, value)
|
|
50
|
+
# Sets attribute `key` to `value` on `self` (not on any parent).
|
|
51
|
+
# Values are validated as JSON-compatible plus non-finite Floats
|
|
52
|
+
# (String / Numeric incl. Infinity / NaN / true / false / nil /
|
|
53
|
+
# Symbol / Array / Hash); Symbol values are coerced to String on
|
|
54
|
+
# store.
|
|
55
|
+
# @param key [Symbol, String] attribute key.
|
|
56
|
+
# @param value [Object] JSON-compatible value.
|
|
57
|
+
# @return [Object] the coerced stored value.
|
|
58
|
+
# @raise [TypeError] when `key` or `value` is not accepted.
|
|
59
|
+
def set_attr (key, value)
|
|
60
|
+
attr_validate_value(value)
|
|
61
|
+
(@attr ||= {})[attr_normalize_key(key)] = attr_coerce_value(value)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @overload attrs
|
|
65
|
+
# Returns a frozen shallow Hash of all attributes visible on
|
|
66
|
+
# `self`, merged along the parent chain (deeper writes shadow
|
|
67
|
+
# shallower ones on a per-key basis).
|
|
68
|
+
# @return [Hash{String => Object}]
|
|
69
|
+
def attrs
|
|
70
|
+
merged = nil
|
|
71
|
+
attr_each_chain do |h|
|
|
72
|
+
merged ||= {}
|
|
73
|
+
h.each { |k, v| merged[k] = v unless merged.key?(k) }
|
|
74
|
+
end
|
|
75
|
+
(merged || {}).freeze
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @overload has_attr?
|
|
79
|
+
# Returns whether `self` (or any parent view) has any attribute
|
|
80
|
+
# set.
|
|
81
|
+
# @return [Boolean]
|
|
82
|
+
# @overload has_attr?(key)
|
|
83
|
+
# Returns whether `self` (or any parent view) has attribute `key`
|
|
84
|
+
# set.
|
|
85
|
+
# @param key [Symbol, String] attribute key.
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def has_attr? (key = nil)
|
|
88
|
+
if key.nil?
|
|
89
|
+
attr_each_chain do |h|
|
|
90
|
+
return true unless h.empty?
|
|
91
|
+
end
|
|
92
|
+
false
|
|
93
|
+
else
|
|
94
|
+
k = attr_normalize_key(key)
|
|
95
|
+
attr_each_chain do |h|
|
|
96
|
+
return true if h.key?(k)
|
|
97
|
+
end
|
|
98
|
+
false
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Yield each non-nil @attr Hash along the parent chain (self first).
|
|
105
|
+
# Bounded to MAX_CHAIN_DEPTH for paranoia against unexpected cycles;
|
|
106
|
+
# real CArray view chains rarely exceed a handful of levels.
|
|
107
|
+
MAX_CHAIN_DEPTH = 64
|
|
108
|
+
private_constant :MAX_CHAIN_DEPTH
|
|
109
|
+
|
|
110
|
+
def attr_each_chain
|
|
111
|
+
ca = self
|
|
112
|
+
MAX_CHAIN_DEPTH.times do
|
|
113
|
+
if ca.instance_variable_defined?(:@attr)
|
|
114
|
+
h = ca.instance_variable_get(:@attr)
|
|
115
|
+
yield(h) if h
|
|
116
|
+
end
|
|
117
|
+
parent = ca.respond_to?(:parent) ? ca.parent : nil
|
|
118
|
+
return nil if parent.nil? || parent.equal?(ca)
|
|
119
|
+
ca = parent
|
|
120
|
+
end
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def attr_normalize_key (key)
|
|
125
|
+
case key
|
|
126
|
+
when String then key
|
|
127
|
+
when Symbol then key.to_s
|
|
128
|
+
else
|
|
129
|
+
raise TypeError, "attribute key must be String or Symbol (got #{key.class})"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def attr_validate_value (v, depth = 0)
|
|
134
|
+
raise ArgumentError, "attribute value nests too deep" if depth > 32
|
|
135
|
+
case v
|
|
136
|
+
when String, Integer, Float, true, false, nil, Symbol
|
|
137
|
+
# ok (Symbol will be coerced to String on store)
|
|
138
|
+
when Array
|
|
139
|
+
v.each { |e| attr_validate_value(e, depth + 1) }
|
|
140
|
+
when Hash
|
|
141
|
+
v.each do |k, vv|
|
|
142
|
+
unless k.is_a?(String) || k.is_a?(Symbol)
|
|
143
|
+
raise TypeError, "nested Hash keys must be String or Symbol (got #{k.class})"
|
|
144
|
+
end
|
|
145
|
+
attr_validate_value(vv, depth + 1)
|
|
146
|
+
end
|
|
147
|
+
else
|
|
148
|
+
raise TypeError, "attribute value must be JSON-compatible " \
|
|
149
|
+
"(String/Numeric/true/false/nil/Symbol/Array/Hash); got #{v.class}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def attr_coerce_value (v)
|
|
154
|
+
case v
|
|
155
|
+
when Symbol then v.to_s
|
|
156
|
+
when Array then v.map { |e| attr_coerce_value(e) }
|
|
157
|
+
when Hash
|
|
158
|
+
v.each_with_object({}) do |(k, vv), h|
|
|
159
|
+
h[k.is_a?(Symbol) ? k.to_s : k] = attr_coerce_value(vv)
|
|
160
|
+
end
|
|
161
|
+
else
|
|
162
|
+
v
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|