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,697 @@
|
|
|
1
|
+
# CAStruct::Builder -- the DSL backend behind CArray.struct { ... } and
|
|
2
|
+
# CArray.union { ... }. Autoloaded from lib/carray/struct.rb on first
|
|
3
|
+
# reference to CAStruct::Builder, so applications that only consume
|
|
4
|
+
# pre-defined struct classes never pay for loading the Builder.
|
|
5
|
+
|
|
6
|
+
# ---------------------------------------------------------------------
|
|
7
|
+
#
|
|
8
|
+
# Struct Builder Class
|
|
9
|
+
#
|
|
10
|
+
# ---------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
class CAStruct::Builder # :nodoc:
|
|
13
|
+
|
|
14
|
+
class Member # :nodoc:
|
|
15
|
+
|
|
16
|
+
def initialize (name, type, opt={})
|
|
17
|
+
@name, @type, @opt = name, type, opt
|
|
18
|
+
if @type == :bitfield
|
|
19
|
+
# Bit members carry their own offset/length math via the
|
|
20
|
+
# Builder's bit() method; the standard byte cursor in
|
|
21
|
+
# member() does not advance for them. Setting byte_length=0
|
|
22
|
+
# keeps any defensive byte-loop arithmetic harmless.
|
|
23
|
+
@bytes = 0
|
|
24
|
+
@byte_length = 0
|
|
25
|
+
elsif @type.kind_of?(CArray)
|
|
26
|
+
@type = @type.copy
|
|
27
|
+
@byte_length = @type.bytes * @type.elements
|
|
28
|
+
@bytes = 0
|
|
29
|
+
else
|
|
30
|
+
data_type, @bytes = CArray.guess_type_and_bytes(@type, @opt[:bytes])
|
|
31
|
+
if data_type == CA_OBJECT
|
|
32
|
+
raise CAStruct::DefinitionError,
|
|
33
|
+
"CA_OBJECT cannot be a member of struct or union"
|
|
34
|
+
end
|
|
35
|
+
@byte_length = @bytes
|
|
36
|
+
end
|
|
37
|
+
@offset = @opt[:offset]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :name, :type, :offset, :bytes, :byte_length, :opt
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Build the DISPATCH_TABLE for a finalized MEMBER_TABLE.
|
|
45
|
+
# Returns `{ name => [reader_proc, writer_proc] }` where each Proc
|
|
46
|
+
# closes over the per-member offset / opts. Called once, at the
|
|
47
|
+
# bottom of `define`. Public so it can be tested in isolation.
|
|
48
|
+
#
|
|
49
|
+
# `data_size` is the struct's DATA_SIZE; needed up front so we can
|
|
50
|
+
# bounds-check bit-field projections at definition time rather than
|
|
51
|
+
# at first access.
|
|
52
|
+
def self.build_dispatch_table (member_table, data_size)
|
|
53
|
+
dispatch = {}
|
|
54
|
+
member_table.each do |name, entry|
|
|
55
|
+
offset, type, opts = *entry
|
|
56
|
+
case
|
|
57
|
+
when type == :bitfield
|
|
58
|
+
dispatch[name] = build_bitfield_dispatcher(name, opts, data_size)
|
|
59
|
+
when type.is_a?(Class)
|
|
60
|
+
dispatch[name] = build_nested_struct_dispatcher(offset, type)
|
|
61
|
+
when type.is_a?(CArray)
|
|
62
|
+
dispatch[name] = build_carray_template_dispatcher(offset, type)
|
|
63
|
+
else
|
|
64
|
+
dispatch[name] = build_primitive_dispatcher(offset, type, opts)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
dispatch
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# CIFY: build the FAST_PRIMITIVES table consumed by the C-native
|
|
71
|
+
# CAStruct#[] / #[]=. Includes the members the C path can handle
|
|
72
|
+
# without allocating any CAField / CABitfield / CAByteSwap view:
|
|
73
|
+
#
|
|
74
|
+
# - Plain primitives (int8..uint64, float32/64) without `endian:`
|
|
75
|
+
# - Bit-fields: word load + shift + mask in C
|
|
76
|
+
# - Endian-tagged primitives: __builtin_bswap in C, but
|
|
77
|
+
# only when the requested endian differs from the host -- when
|
|
78
|
+
# they match, the member is folded into the primitive path with
|
|
79
|
+
# no swap at all.
|
|
80
|
+
#
|
|
81
|
+
# Bit-fields whose spanning power-of-2 word reaches past
|
|
82
|
+
# `data_size` cannot be fast-pathed (CABitfield's existing guard
|
|
83
|
+
# also rejects them); they fall through to DISPATCH_TABLE so the
|
|
84
|
+
# Ruby Proc can produce a clear DefinitionError-style failure.
|
|
85
|
+
#
|
|
86
|
+
# Nested-struct / CArray-template / fixlen / cmplx / object stay
|
|
87
|
+
# absent; for them the C path falls back to DISPATCH_TABLE's Proc.
|
|
88
|
+
#
|
|
89
|
+
# Entry shape: `name => [kind, ...args]` (frozen Array).
|
|
90
|
+
#
|
|
91
|
+
# FAST_KIND_PRIMITIVE = 0
|
|
92
|
+
# [0, offset, ca_type_code]
|
|
93
|
+
# FAST_KIND_BITFIELD = 1
|
|
94
|
+
# [1, start_byte, view_bytes, bit_in_word, bits]
|
|
95
|
+
# FAST_KIND_ENDIAN = 2
|
|
96
|
+
# [2, offset, ca_type_code]
|
|
97
|
+
#
|
|
98
|
+
# Kind tags are kept in sync with FAST_KIND_* in
|
|
99
|
+
# ext/carray_struct.c.
|
|
100
|
+
FAST_KIND_PRIMITIVE = 0
|
|
101
|
+
# @!visibility private
|
|
102
|
+
FAST_KIND_BITFIELD = 1
|
|
103
|
+
# @!visibility private
|
|
104
|
+
FAST_KIND_ENDIAN = 2
|
|
105
|
+
|
|
106
|
+
# @!visibility private
|
|
107
|
+
def self.build_fast_primitives (member_table, data_size = nil)
|
|
108
|
+
fast = {}
|
|
109
|
+
host_endian = CArray.endian
|
|
110
|
+
member_table.each do |name, entry|
|
|
111
|
+
offset, type, opts = *entry
|
|
112
|
+
case
|
|
113
|
+
when type == :bitfield
|
|
114
|
+
fast_entry = build_fast_bitfield_entry(opts, data_size)
|
|
115
|
+
fast[name] = fast_entry if fast_entry
|
|
116
|
+
when type.is_a?(Class)
|
|
117
|
+
# nested struct — DISPATCH_TABLE
|
|
118
|
+
when type.is_a?(CArray)
|
|
119
|
+
# CArray template — DISPATCH_TABLE
|
|
120
|
+
else
|
|
121
|
+
type_code = FAST_PRIMITIVE_TYPE_CODES[type]
|
|
122
|
+
next unless type_code
|
|
123
|
+
endian = opts && opts[:endian]
|
|
124
|
+
if endian && endian_needs_swap?(endian, host_endian) &&
|
|
125
|
+
ENDIAN_FAST_TYPE_CODES.include?(type_code)
|
|
126
|
+
fast[name] = [FAST_KIND_ENDIAN, offset, type_code].freeze
|
|
127
|
+
else
|
|
128
|
+
fast[name] = [FAST_KIND_PRIMITIVE, offset, type_code].freeze
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
fast
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Bit-field FAST entry, mirroring build_bitfield_dispatcher's
|
|
136
|
+
# geometry calculation but emitting the C-friendly tuple
|
|
137
|
+
# [FAST_KIND_BITFIELD, start_byte, view_bytes, bit_in_word, bits].
|
|
138
|
+
# Returns nil if the field can't be fast-pathed (e.g. its spanning
|
|
139
|
+
# word reaches past DATA_SIZE -- the existing build_bitfield_dispatcher
|
|
140
|
+
# raises in that case at definition time; here we just bow out and
|
|
141
|
+
# let DISPATCH_TABLE raise).
|
|
142
|
+
def self.build_fast_bitfield_entry (opts, data_size)
|
|
143
|
+
bits = opts[:bits]
|
|
144
|
+
bit_offset = opts[:bit_offset]
|
|
145
|
+
start_byte = bit_offset / 8
|
|
146
|
+
bit_in_word = bit_offset % 8
|
|
147
|
+
span = (bit_offset + bits + 7) / 8 - start_byte
|
|
148
|
+
view_bytes = case
|
|
149
|
+
when span <= 1 then 1
|
|
150
|
+
when span <= 2 then 2
|
|
151
|
+
when span <= 4 then 4
|
|
152
|
+
else 8
|
|
153
|
+
end
|
|
154
|
+
return nil if data_size && start_byte + view_bytes > data_size
|
|
155
|
+
[FAST_KIND_BITFIELD, start_byte, view_bytes, bit_in_word, bits].freeze
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# True iff a member tagged with the given `endian:` keyword needs
|
|
159
|
+
# an actual byte swap on the current host. Matches CArray#endian's
|
|
160
|
+
# short-circuit logic: :preserve and :native are always identity;
|
|
161
|
+
# :big / :little are identity when the host already matches.
|
|
162
|
+
def self.endian_needs_swap? (endian_sym, host_endian)
|
|
163
|
+
case endian_sym
|
|
164
|
+
when :preserve, :native then false
|
|
165
|
+
when :big then host_endian != CA_BIG_ENDIAN
|
|
166
|
+
when :little then host_endian != CA_LITTLE_ENDIAN
|
|
167
|
+
else false # unknown sym — be conservative, defer to DISPATCH_TABLE
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Symbol → CA_* integer constant for the primitive numeric types
|
|
172
|
+
# the C fast path handles directly. Anything missing here routes
|
|
173
|
+
# through DISPATCH_TABLE (slower but correct).
|
|
174
|
+
# These values are consumed by the C-side FAST_PRIMITIVES dispatcher
|
|
175
|
+
# (ext/carray_struct.c) which expects raw int8_t data_type codes via
|
|
176
|
+
# NUM2INT. CA_* are Symbols, so we eagerly convert to Integer codes
|
|
177
|
+
# here at definition time.
|
|
178
|
+
FAST_PRIMITIVE_TYPE_CODES = {
|
|
179
|
+
:boolean => CArray.data_type_code(CA_BOOLEAN),
|
|
180
|
+
:int8 => CArray.data_type_code(CA_INT8),
|
|
181
|
+
:uint8 => CArray.data_type_code(CA_UINT8),
|
|
182
|
+
:int16 => CArray.data_type_code(CA_INT16),
|
|
183
|
+
:uint16 => CArray.data_type_code(CA_UINT16),
|
|
184
|
+
:int32 => CArray.data_type_code(CA_INT32),
|
|
185
|
+
:uint32 => CArray.data_type_code(CA_UINT32),
|
|
186
|
+
:int64 => CArray.data_type_code(CA_INT64),
|
|
187
|
+
:uint64 => CArray.data_type_code(CA_UINT64),
|
|
188
|
+
:float32 => CArray.data_type_code(CA_FLOAT32),
|
|
189
|
+
:float64 => CArray.data_type_code(CA_FLOAT64),
|
|
190
|
+
}.freeze
|
|
191
|
+
|
|
192
|
+
# ca_type_code → eligible for the endian-swapped fast path. 1-byte
|
|
193
|
+
# types are excluded because byte-swap is a no-op (they go through
|
|
194
|
+
# the plain primitive path). Complex types are excluded too;
|
|
195
|
+
# their per-component swap matches CAByteSwap behaviour and stays
|
|
196
|
+
# on the DISPATCH_TABLE Proc. Values are Integer codes (same
|
|
197
|
+
# rationale as FAST_PRIMITIVE_TYPE_CODES above).
|
|
198
|
+
ENDIAN_FAST_TYPE_CODES = FAST_PRIMITIVE_TYPE_CODES.values_at(
|
|
199
|
+
:int16, :uint16, :int32, :uint32, :int64, :uint64,
|
|
200
|
+
:float32, :float64,
|
|
201
|
+
).freeze
|
|
202
|
+
|
|
203
|
+
# -- per-kind dispatcher builders -------------------------------------
|
|
204
|
+
|
|
205
|
+
def self.build_bitfield_dispatcher (name, opts, data_size)
|
|
206
|
+
bits = opts[:bits]
|
|
207
|
+
bit_offset = opts[:bit_offset]
|
|
208
|
+
start_byte = bit_offset / 8
|
|
209
|
+
bit_in_byte = bit_offset % 8
|
|
210
|
+
span = (bit_offset + bits + 7) / 8 - start_byte
|
|
211
|
+
view_bytes = case
|
|
212
|
+
when span <= 1 then 1
|
|
213
|
+
when span <= 2 then 2
|
|
214
|
+
when span <= 4 then 4
|
|
215
|
+
else 8
|
|
216
|
+
end
|
|
217
|
+
if start_byte + view_bytes > data_size
|
|
218
|
+
raise CAStruct::DefinitionError,
|
|
219
|
+
"bit member #{name.inspect} (bit_offset=#{bit_offset}, " \
|
|
220
|
+
"bits=#{bits}) needs a #{view_bytes}-byte view starting " \
|
|
221
|
+
"at byte #{start_byte}, but the record is only " \
|
|
222
|
+
"#{data_size} bytes — deferred to A.3+ (multi-byte bit " \
|
|
223
|
+
"members spanning unaligned record tails)"
|
|
224
|
+
end
|
|
225
|
+
vtype = {1 => :uint8, 2 => :uint16, 4 => :uint32, 8 => :uint64}[view_bytes]
|
|
226
|
+
range = bit_in_byte..(bit_in_byte + bits - 1)
|
|
227
|
+
reader = ->(data) {
|
|
228
|
+
data.field(start_byte, vtype).bitfield(range)[0]
|
|
229
|
+
}
|
|
230
|
+
writer = ->(data, val) {
|
|
231
|
+
data.field(start_byte, vtype).bitfield(range)[0] = val
|
|
232
|
+
}
|
|
233
|
+
[reader, writer].freeze
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# @!visibility private
|
|
237
|
+
def self.build_nested_struct_dispatcher (offset, nested_class)
|
|
238
|
+
reader = ->(data) { nested_class.decode(data.field(offset, nested_class)) }
|
|
239
|
+
writer = ->(data, val) { data.field(offset, nested_class)[0] = val }
|
|
240
|
+
[reader, writer].freeze
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# @!visibility private
|
|
244
|
+
def self.build_carray_template_dispatcher (offset, template)
|
|
245
|
+
reader = ->(data) { data.field(offset, template)[0, false] }
|
|
246
|
+
writer = ->(data, val) { data.field(offset, template)[0, false] = val }
|
|
247
|
+
[reader, writer].freeze
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# @!visibility private
|
|
251
|
+
def self.build_primitive_dispatcher (offset, type, opts)
|
|
252
|
+
# Specialise the reader/writer to the smallest amount of work
|
|
253
|
+
# this particular member needs: no opts → 2-arg field; opts with
|
|
254
|
+
# only :bytes → 3-arg field; opts with :endian → 3-arg field +
|
|
255
|
+
# endian view chain.
|
|
256
|
+
endian = opts && opts[:endian]
|
|
257
|
+
if opts.nil?
|
|
258
|
+
reader = ->(data) { data.field(offset, type)[0] }
|
|
259
|
+
writer = ->(data, val) { data.field(offset, type)[0] = val }
|
|
260
|
+
elsif endian.nil?
|
|
261
|
+
reader = ->(data) { data.field(offset, type, opts)[0] }
|
|
262
|
+
writer = ->(data, val) { data.field(offset, type, opts)[0] = val }
|
|
263
|
+
else
|
|
264
|
+
# CArray#field only accepts `bytes:`; the endian swap is applied by
|
|
265
|
+
# the chained #endian view, not by field. Strip :endian from the
|
|
266
|
+
# hash handed to field so it is not rejected.
|
|
267
|
+
field_opts = opts.reject { |k, _| k == :endian }
|
|
268
|
+
if field_opts.empty?
|
|
269
|
+
reader = ->(data) { data.field(offset, type).endian(endian)[0] }
|
|
270
|
+
writer = ->(data, val) { data.field(offset, type).endian(endian)[0] = val }
|
|
271
|
+
else
|
|
272
|
+
reader = ->(data) { data.field(offset, type, field_opts).endian(endian)[0] }
|
|
273
|
+
writer = ->(data, val) {
|
|
274
|
+
data.field(offset, type, field_opts).endian(endian)[0] = val
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
[reader, writer].freeze
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @!visibility private
|
|
282
|
+
ALIGN_TABLE = {
|
|
283
|
+
CA_FIXLEN => CA_ALIGN_FIXLEN,
|
|
284
|
+
CA_BOOLEAN => CA_ALIGN_INT8,
|
|
285
|
+
CA_INT8 => CA_ALIGN_INT8,
|
|
286
|
+
CA_UINT8 => CA_ALIGN_INT8,
|
|
287
|
+
CA_INT16 => CA_ALIGN_INT16,
|
|
288
|
+
CA_UINT16 => CA_ALIGN_INT16,
|
|
289
|
+
CA_INT32 => CA_ALIGN_INT32,
|
|
290
|
+
CA_UINT32 => CA_ALIGN_INT32,
|
|
291
|
+
CA_INT64 => CA_ALIGN_INT64,
|
|
292
|
+
CA_UINT64 => CA_ALIGN_INT64,
|
|
293
|
+
CA_FLOAT32 => CA_ALIGN_FLOAT32,
|
|
294
|
+
CA_FLOAT64 => CA_ALIGN_FLOAT64,
|
|
295
|
+
CA_CMPLX64 => CA_ALIGN_FLOAT32,
|
|
296
|
+
CA_CMPLX128 => CA_ALIGN_FLOAT64,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
def initialize (type, opt = {})
|
|
300
|
+
if not opt[:pack].nil? and not opt[:pack].is_a?(Integer)
|
|
301
|
+
raise CAStruct::DefinitionError,
|
|
302
|
+
"invalid :pack value #{opt[:pack].inspect} (expected nil or Integer)"
|
|
303
|
+
end
|
|
304
|
+
@type = type ### :struct or :union
|
|
305
|
+
@align = opt[:pack] ### nil for alignment, int for pack(n)
|
|
306
|
+
@members = [] ### array of CArray::Struct::Builder::Member
|
|
307
|
+
@offset = 0 ### offset of each member and size of struct
|
|
308
|
+
@align_max = 1 ### maximum of alignment among members
|
|
309
|
+
@size = opt[:size] ### user defined struct size
|
|
310
|
+
@bit_offset = 0 ### 0..7, sub-byte position for bitfield accumulator
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Round up the byte cursor if the bitfield accumulator is mid-byte.
|
|
314
|
+
# Called before placing a non-bit member so the byte member starts
|
|
315
|
+
# on a clean byte boundary (and after the body is fully built so
|
|
316
|
+
# DATA_SIZE includes the trailing bits' byte).
|
|
317
|
+
def flush_bit_offset
|
|
318
|
+
if @bit_offset > 0
|
|
319
|
+
@offset += 1
|
|
320
|
+
@bit_offset = 0
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# @!visibility private
|
|
325
|
+
def define (&block)
|
|
326
|
+
# ---
|
|
327
|
+
case block.arity
|
|
328
|
+
when 1
|
|
329
|
+
block.call(self) ### struct/union definition block
|
|
330
|
+
when -1, 0
|
|
331
|
+
instance_exec(&block) ### struct/union definition block
|
|
332
|
+
else
|
|
333
|
+
raise CAStruct::DefinitionError,
|
|
334
|
+
"invalid # of block parameters (expected 0 or 1)"
|
|
335
|
+
end
|
|
336
|
+
# Flush any trailing bit accumulator so DATA_SIZE includes the
|
|
337
|
+
# final byte that carries the last bit member's tail.
|
|
338
|
+
flush_bit_offset
|
|
339
|
+
# ---
|
|
340
|
+
# Detect duplicate member names before doing anything else. A
|
|
341
|
+
# duplicate would silently overwrite MEMBER_TABLE entries and
|
|
342
|
+
# produce a struct whose MEMBERS list contains the same name
|
|
343
|
+
# twice, which downstream code (MEMBERS.map { ... }, define_method
|
|
344
|
+
# loop, .spec) cannot recover from cleanly.
|
|
345
|
+
seen = {}
|
|
346
|
+
dups = []
|
|
347
|
+
@members.each do |mem|
|
|
348
|
+
if seen[mem.name]
|
|
349
|
+
dups << mem.name
|
|
350
|
+
else
|
|
351
|
+
seen[mem.name] = true
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
unless dups.empty?
|
|
355
|
+
raise CAStruct::DefinitionError,
|
|
356
|
+
format("duplicate member name(s): %s",
|
|
357
|
+
dups.uniq.map(&:inspect).join(", "))
|
|
358
|
+
end
|
|
359
|
+
# ---
|
|
360
|
+
case @type
|
|
361
|
+
when :struct
|
|
362
|
+
klass = Class.new(CAStruct)
|
|
363
|
+
when :union
|
|
364
|
+
klass = Class.new(CAUnion)
|
|
365
|
+
end
|
|
366
|
+
# ---
|
|
367
|
+
if @align.nil?
|
|
368
|
+
@offset = alignment(@offset, :align_max)
|
|
369
|
+
end
|
|
370
|
+
if @size
|
|
371
|
+
if @size < @offset
|
|
372
|
+
raise CAStruct::DefinitionError,
|
|
373
|
+
format("declared :size (%d) is smaller than packed body (%d)",
|
|
374
|
+
@size, @offset)
|
|
375
|
+
end
|
|
376
|
+
@offset = @size
|
|
377
|
+
end
|
|
378
|
+
klass.const_set(:DATA_SIZE, @offset) ### required element as data class
|
|
379
|
+
# ---
|
|
380
|
+
table = Hash.new
|
|
381
|
+
names = []
|
|
382
|
+
@members.each do |mem|
|
|
383
|
+
name = mem.name
|
|
384
|
+
type = mem.type
|
|
385
|
+
offset = mem.offset
|
|
386
|
+
bytes = mem.bytes
|
|
387
|
+
if type == :bitfield
|
|
388
|
+
# Bit members carry their addressing info (bits + bit_offset)
|
|
389
|
+
# in opts; CAStruct#[] / #[]= dispatches on this type and
|
|
390
|
+
# routes to @data.bitfield(bit_offset, bits).
|
|
391
|
+
table[name] = [offset, :bitfield,
|
|
392
|
+
{ :bits => mem.opt[:bits],
|
|
393
|
+
:bit_offset => mem.opt[:bit_offset] }]
|
|
394
|
+
elsif bytes
|
|
395
|
+
ent = {:bytes => bytes}
|
|
396
|
+
ent[:endian] = mem.opt[:endian] if mem.opt[:endian]
|
|
397
|
+
table[name] = [offset, type, ent]
|
|
398
|
+
elsif mem.opt[:endian]
|
|
399
|
+
table[name] = [offset, type, {:endian => mem.opt[:endian]}]
|
|
400
|
+
else
|
|
401
|
+
table[name] = [offset, type]
|
|
402
|
+
end
|
|
403
|
+
names.push(name)
|
|
404
|
+
end
|
|
405
|
+
table.freeze
|
|
406
|
+
names.freeze
|
|
407
|
+
klass.const_set(:MEMBER_TABLE, table) ### required element as data class
|
|
408
|
+
klass.const_set(:MEMBERS, names)
|
|
409
|
+
# ---
|
|
410
|
+
# Step 3 dispatch table: precompute a per-member [reader, writer]
|
|
411
|
+
# Proc pair so `record[name]` is one Hash lookup + one Proc call,
|
|
412
|
+
# with no `case type` reclassification on each access. All bounds
|
|
413
|
+
# checks and view-shape decisions happen here, once, at class
|
|
414
|
+
# definition time.
|
|
415
|
+
klass.const_set(:DISPATCH_TABLE,
|
|
416
|
+
self.class.build_dispatch_table(table, @offset).freeze)
|
|
417
|
+
# Also precompute FAST_PRIMITIVES, the subset of members that the
|
|
418
|
+
# C-side `[]` / `[]=` can handle without
|
|
419
|
+
# allocating a CAField view. Entries are `[offset, type_code]`;
|
|
420
|
+
# the C reader does a typed memcpy + INT2FIX/DBL2NUM/... directly
|
|
421
|
+
# on @data.ptr + offset. Bit / endian / nested / template / fixlen
|
|
422
|
+
# members are absent here -- they stay on the DISPATCH_TABLE Proc
|
|
423
|
+
# path.
|
|
424
|
+
klass.const_set(:FAST_PRIMITIVES,
|
|
425
|
+
self.class.build_fast_primitives(table, @offset).freeze)
|
|
426
|
+
# ---
|
|
427
|
+
klass.module_eval {
|
|
428
|
+
names.each do |name|
|
|
429
|
+
define_method(name) {
|
|
430
|
+
return self[name]
|
|
431
|
+
}
|
|
432
|
+
define_method("#{name}=") { |val|
|
|
433
|
+
return self[name] = val
|
|
434
|
+
}
|
|
435
|
+
end
|
|
436
|
+
}
|
|
437
|
+
# ---
|
|
438
|
+
return klass
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
private
|
|
442
|
+
|
|
443
|
+
def alignment (addr, data_type, opt={})
|
|
444
|
+
# ALIGN_TABLE keys are CA_* constants, which are Symbols. Dispatch on
|
|
445
|
+
# Symbol directly; the Integer path (= raw data_type code) maps via the
|
|
446
|
+
# canonical Symbol name.
|
|
447
|
+
case data_type
|
|
448
|
+
when :align_max
|
|
449
|
+
align = @align_max
|
|
450
|
+
when Symbol
|
|
451
|
+
align = ALIGN_TABLE[data_type]
|
|
452
|
+
unless align
|
|
453
|
+
data_type, bytes = CArray.guess_type_and_bytes(data_type, opt[:bytes])
|
|
454
|
+
return alignment(addr, data_type, :bytes=>bytes)
|
|
455
|
+
end
|
|
456
|
+
when Integer
|
|
457
|
+
align = ALIGN_TABLE[CArray.data_type_name(data_type).to_sym]
|
|
458
|
+
when CArray, Class
|
|
459
|
+
align = CA_ALIGN_VOIDP
|
|
460
|
+
else
|
|
461
|
+
data_type, bytes = CArray.guess_type_and_bytes(data_type, opt[:bytes])
|
|
462
|
+
return alignment(addr, data_type, :bytes=>bytes)
|
|
463
|
+
end
|
|
464
|
+
if align > @align_max
|
|
465
|
+
@align_max = align
|
|
466
|
+
end
|
|
467
|
+
if ( d = addr % align ) != 0
|
|
468
|
+
addr += (align - d)
|
|
469
|
+
end
|
|
470
|
+
return addr
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def pack (addr, align, opt={})
|
|
474
|
+
if ( addr % align ) != 0
|
|
475
|
+
raise CAStruct::DefinitionError,
|
|
476
|
+
format("invalid offset for packing: %d not aligned to %d", addr, align)
|
|
477
|
+
end
|
|
478
|
+
return addr
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
public
|
|
482
|
+
|
|
483
|
+
# @overload member(data_type, id = nil, opt = {})
|
|
484
|
+
# Declares a member with the given storage `data_type`. Used
|
|
485
|
+
# internally by the typed DSL methods (`int32`, `float64`, ...);
|
|
486
|
+
# call directly to place a custom template or nested type.
|
|
487
|
+
# @param data_type [Object] data type (Symbol, Integer, CArray
|
|
488
|
+
# template, or nested struct class).
|
|
489
|
+
# @param id [Symbol, String, nil] member name; auto-generated
|
|
490
|
+
# when `nil`.
|
|
491
|
+
# @param opt [Hash] member options (`:offset`, `:bytes`, `:endian`, ...).
|
|
492
|
+
# @return [Member]
|
|
493
|
+
def member (data_type, id = nil, opt = {})
|
|
494
|
+
opt = opt.clone
|
|
495
|
+
# If a bit member sequence was in flight, round up to a clean byte
|
|
496
|
+
# boundary before placing this byte-typed member.
|
|
497
|
+
flush_bit_offset
|
|
498
|
+
if id
|
|
499
|
+
id = id.to_s
|
|
500
|
+
else
|
|
501
|
+
id = "#{@members.size}"
|
|
502
|
+
end
|
|
503
|
+
case @type
|
|
504
|
+
when :struct ### struct
|
|
505
|
+
case @align
|
|
506
|
+
when nil ### -- aligned
|
|
507
|
+
@offset = alignment(@offset, data_type, opt)
|
|
508
|
+
opt[:offset] = @offset
|
|
509
|
+
else ### -- packed
|
|
510
|
+
if opt[:offset] ### ---- explicit offset
|
|
511
|
+
@offset = pack(opt[:offset], @align, opt)
|
|
512
|
+
else
|
|
513
|
+
opt[:offset] = @offset ### ---- auto offset
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
mem = Member.new(id, data_type, opt)
|
|
517
|
+
@members.push(mem)
|
|
518
|
+
@offset += mem.byte_length
|
|
519
|
+
when :union ### union
|
|
520
|
+
alignment(0, data_type, opt)
|
|
521
|
+
opt[:offset] = 0
|
|
522
|
+
mem = Member.new(id, data_type, opt)
|
|
523
|
+
@members.push(mem)
|
|
524
|
+
if mem.byte_length > @offset
|
|
525
|
+
@offset = mem.byte_length
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# @overload struct(*names, &block)
|
|
531
|
+
# Declares a nested struct member. With a block, defines an
|
|
532
|
+
# inline nested struct; otherwise requires `type:` naming an
|
|
533
|
+
# existing {CAStruct} subclass.
|
|
534
|
+
# @param names [Array<Symbol>] member names.
|
|
535
|
+
# @return [Class] the nested struct class.
|
|
536
|
+
# @raise [CAStruct::DefinitionError] when neither a block nor
|
|
537
|
+
# `type:` is provided.
|
|
538
|
+
def struct (*args, &block)
|
|
539
|
+
opt = args.last.is_a?(Hash) ? args.pop : {}
|
|
540
|
+
if block
|
|
541
|
+
opt = {:pack => @align}.update(opt)
|
|
542
|
+
st = self.class.new(:struct, opt).define(&block)
|
|
543
|
+
elsif opt[:type] and opt[:type] <= CAStruct
|
|
544
|
+
st = opt[:type]
|
|
545
|
+
else
|
|
546
|
+
raise CAStruct::DefinitionError,
|
|
547
|
+
"no type given for nested struct member (pass a block or :type)"
|
|
548
|
+
end
|
|
549
|
+
args.each do |arg|
|
|
550
|
+
member(st, arg)
|
|
551
|
+
end
|
|
552
|
+
return st
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# @overload union(*names, &block)
|
|
556
|
+
# Declares a nested union member. With a block, defines an
|
|
557
|
+
# inline nested union; otherwise requires `type:` naming an
|
|
558
|
+
# existing {CAUnion} subclass.
|
|
559
|
+
# @param names [Array<Symbol>] member names.
|
|
560
|
+
# @return [Class] the nested union class.
|
|
561
|
+
# @raise [CAStruct::DefinitionError] when neither a block nor
|
|
562
|
+
# `type:` is provided.
|
|
563
|
+
def union (*args, &block)
|
|
564
|
+
opt = args.last.is_a?(Hash) ? args.pop : {}
|
|
565
|
+
if block
|
|
566
|
+
opt = {:pack => @align}.update(opt)
|
|
567
|
+
st = self.class.new(:union, opt).define(&block)
|
|
568
|
+
elsif opt[:type] and opt[:type] <= CAStruct
|
|
569
|
+
st = opt[:type]
|
|
570
|
+
else
|
|
571
|
+
raise CAStruct::DefinitionError,
|
|
572
|
+
"no type given for nested union member (pass a block or :type)"
|
|
573
|
+
end
|
|
574
|
+
args.each do |arg|
|
|
575
|
+
member(st, arg)
|
|
576
|
+
end
|
|
577
|
+
return st
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
# @overload array(*names, type:)
|
|
581
|
+
# Declares one or more members holding a CArray template.
|
|
582
|
+
# @param names [Array<Symbol>] member names.
|
|
583
|
+
# @return [void]
|
|
584
|
+
# @raise [CAStruct::DefinitionError] when `type:` is not a
|
|
585
|
+
# CArray template.
|
|
586
|
+
def array (*args)
|
|
587
|
+
opt = args.last.is_a?(Hash) ? args.pop : {}
|
|
588
|
+
if not opt[:type] or not opt[:type].kind_of?(CArray)
|
|
589
|
+
raise CAStruct::DefinitionError,
|
|
590
|
+
"no :type given for array member (expected a CArray template)"
|
|
591
|
+
end
|
|
592
|
+
args.each do |arg|
|
|
593
|
+
member(opt[:type], arg)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# @overload bit(name, bits:)
|
|
598
|
+
# Declares a bitfield member. Bits accumulate within the
|
|
599
|
+
# current byte; when a byte member follows, the cursor rounds
|
|
600
|
+
# up to the next byte boundary. Only supported in `:struct`
|
|
601
|
+
# (not `:union`) and always requires a name.
|
|
602
|
+
# @param name [Symbol, String] member name.
|
|
603
|
+
# @param bits [Integer] bit width in `1..64`.
|
|
604
|
+
# @return [void]
|
|
605
|
+
# @raise [CAStruct::DefinitionError] on `:union` context, a
|
|
606
|
+
# missing name, or an invalid `bits` value.
|
|
607
|
+
def bit (name, bits:)
|
|
608
|
+
if @type == :union
|
|
609
|
+
raise CAStruct::DefinitionError,
|
|
610
|
+
"bit members in :union are not supported"
|
|
611
|
+
end
|
|
612
|
+
if name.nil?
|
|
613
|
+
raise CAStruct::DefinitionError,
|
|
614
|
+
"bit member must have a name (no anonymous bit padding)"
|
|
615
|
+
end
|
|
616
|
+
unless bits.is_a?(Integer) && bits >= 1 && bits <= 64
|
|
617
|
+
raise CAStruct::DefinitionError,
|
|
618
|
+
"bit member requires bits: as an Integer in 1..64 (got #{bits.inspect})"
|
|
619
|
+
end
|
|
620
|
+
total_bit_offset = @offset * 8 + @bit_offset
|
|
621
|
+
mem = Member.new(name.to_s, :bitfield,
|
|
622
|
+
{:bits => bits,
|
|
623
|
+
:bit_offset => total_bit_offset,
|
|
624
|
+
:offset => @offset})
|
|
625
|
+
@members.push(mem)
|
|
626
|
+
@bit_offset += bits
|
|
627
|
+
while @bit_offset >= 8
|
|
628
|
+
@offset += 1
|
|
629
|
+
@bit_offset -= 8
|
|
630
|
+
end
|
|
631
|
+
if @align_max < 1
|
|
632
|
+
@align_max = 1
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
# @!visibility private
|
|
637
|
+
VALID_ENDIAN = [:preserve, :native, :big, :little].freeze
|
|
638
|
+
|
|
639
|
+
# Validate the `endian:` option on a typed-member declaration.
|
|
640
|
+
# `allow` is false for non-numeric types (e.g. fixlen) where a
|
|
641
|
+
# byte-swap view would mangle the value.
|
|
642
|
+
def self.validate_endian! (opt, typename, allow)
|
|
643
|
+
return unless opt.key?(:endian)
|
|
644
|
+
unless allow
|
|
645
|
+
raise CAStruct::DefinitionError,
|
|
646
|
+
"endian: is not supported for #{typename} members"
|
|
647
|
+
end
|
|
648
|
+
unless VALID_ENDIAN.include?(opt[:endian])
|
|
649
|
+
raise CAStruct::DefinitionError,
|
|
650
|
+
"endian: must be one of #{VALID_ENDIAN.inspect} " \
|
|
651
|
+
"(got #{opt[:endian].inspect})"
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
[
|
|
656
|
+
["fixlen", false],
|
|
657
|
+
["boolean", true],
|
|
658
|
+
["int8", true],
|
|
659
|
+
["uint8", true],
|
|
660
|
+
["int16", true],
|
|
661
|
+
["uint16", true],
|
|
662
|
+
["int32", true],
|
|
663
|
+
["uint32", true],
|
|
664
|
+
["int64", true],
|
|
665
|
+
["uint64", true],
|
|
666
|
+
["float32", true],
|
|
667
|
+
["float64", true],
|
|
668
|
+
["cmplx64", true],
|
|
669
|
+
["cmplx128", true],
|
|
670
|
+
].each do |typename, allow_endian|
|
|
671
|
+
class_eval %{
|
|
672
|
+
def #{typename} (*args)
|
|
673
|
+
opt = args.last.is_a?(Hash) ? args.pop : {}
|
|
674
|
+
if args.empty?
|
|
675
|
+
raise CAStruct::DefinitionError,
|
|
676
|
+
"#{typename} requires at least one member name " \
|
|
677
|
+
"(use nil for anonymous padding)"
|
|
678
|
+
end
|
|
679
|
+
CAStruct::Builder.validate_endian!(opt, "#{typename}", #{allow_endian})
|
|
680
|
+
args.each do |arg|
|
|
681
|
+
member(:#{typename}, arg, opt)
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
}
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
alias char_p fixlen
|
|
688
|
+
alias byte uint8
|
|
689
|
+
alias short int16
|
|
690
|
+
alias int int32
|
|
691
|
+
alias float float32
|
|
692
|
+
alias double float64
|
|
693
|
+
alias complex cmplx64
|
|
694
|
+
alias dcomplex cmplx128
|
|
695
|
+
|
|
696
|
+
end
|
|
697
|
+
|