Class: CAStruct::Builder
- Inherits:
-
Object
- Object
- CAStruct::Builder
- Defined in:
- lib/carray/struct_builder.rb
Overview
Struct Builder Class
Defined Under Namespace
Classes: Member
Constant Summary collapse
- FAST_KIND_PRIMITIVE =
CIFY: build the FAST_PRIMITIVES table consumed by the C-native CAStruct#[] / #[]=. Includes the members the C path can handle without allocating any CAField / CABitfield / CAByteSwap view:
- Plain primitives (int8..uint64, float32/64) without
endian: - Bit-fields: word load + shift + mask in C
- Endian-tagged primitives: __builtin_bswap in C, but only when the requested endian differs from the host -- when they match, the member is folded into the primitive path with no swap at all.
Bit-fields whose spanning power-of-2 word reaches past
data_sizecannot be fast-pathed (CABitfield's existing guard also rejects them); they fall through to DISPATCH_TABLE so the Ruby Proc can produce a clear DefinitionError-style failure.Nested-struct / CArray-template / fixlen / cmplx / object stay absent; for them the C path falls back to DISPATCH_TABLE's Proc.
Entry shape:
name => [kind, ...args](frozen Array).FAST_KIND_PRIMITIVE = 0 [0, offset, ca_type_code] FAST_KIND_BITFIELD = 1 [1, start_byte, view_bytes, bit_in_word, bits] FAST_KIND_ENDIAN = 2 [2, offset, ca_type_code]
Kind tags are kept in sync with FAST_KIND_* in ext/carray_struct.c.
- Plain primitives (int8..uint64, float32/64) without
0- FAST_PRIMITIVE_TYPE_CODES =
Symbol → CA_* integer constant for the primitive numeric types the C fast path handles directly. Anything missing here routes through DISPATCH_TABLE (slower but correct). These values are consumed by the C-side FAST_PRIMITIVES dispatcher (ext/carray_struct.c) which expects raw int8_t data_type codes via NUM2INT. CA_* are Symbols, so we eagerly convert to Integer codes here at definition time.
{ :boolean => CArray.data_type_code(CA_BOOLEAN), :int8 => CArray.data_type_code(CA_INT8), :uint8 => CArray.data_type_code(CA_UINT8), :int16 => CArray.data_type_code(CA_INT16), :uint16 => CArray.data_type_code(CA_UINT16), :int32 => CArray.data_type_code(CA_INT32), :uint32 => CArray.data_type_code(CA_UINT32), :int64 => CArray.data_type_code(CA_INT64), :uint64 => CArray.data_type_code(CA_UINT64), :float32 => CArray.data_type_code(CA_FLOAT32), :float64 => CArray.data_type_code(CA_FLOAT64), }.freeze
- ENDIAN_FAST_TYPE_CODES =
ca_type_code → eligible for the endian-swapped fast path. 1-byte types are excluded because byte-swap is a no-op (they go through the plain primitive path). Complex types are excluded too; their per-component swap matches CAByteSwap behaviour and stays on the DISPATCH_TABLE Proc. Values are Integer codes (same rationale as FAST_PRIMITIVE_TYPE_CODES above).
FAST_PRIMITIVE_TYPE_CODES.values_at( :int16, :uint16, :int32, :uint32, :int64, :uint64, :float32, :float64, ).freeze
Class Method Summary collapse
-
.build_bitfield_dispatcher(name, opts, data_size) ⇒ Object
-- per-kind dispatcher builders -------------------------------------.
-
.build_dispatch_table(member_table, data_size) ⇒ Object
Build the DISPATCH_TABLE for a finalized MEMBER_TABLE.
-
.build_fast_bitfield_entry(opts, data_size) ⇒ Object
Bit-field FAST entry, mirroring build_bitfield_dispatcher's geometry calculation but emitting the C-friendly tuple [FAST_KIND_BITFIELD, start_byte, view_bytes, bit_in_word, bits].
-
.endian_needs_swap?(endian_sym, host_endian) ⇒ Boolean
True iff a member tagged with the given
endian:keyword needs an actual byte swap on the current host. -
.validate_endian!(opt, typename, allow) ⇒ Object
Validate the
endian:option on a typed-member declaration.
Instance Method Summary collapse
-
#array(*names, type:) ⇒ void
Declares one or more members holding a CArray template.
-
#bit(name, bits:) ⇒ void
Declares a bitfield member.
-
#flush_bit_offset ⇒ Object
Round up the byte cursor if the bitfield accumulator is mid-byte.
-
#initialize(type, opt = {}) ⇒ Builder
constructor
A new instance of Builder.
-
#member(data_type, id = nil, opt = {}) ⇒ Member
Declares a member with the given storage
data_type. -
#struct(*names, &block) ⇒ Class
Declares a nested struct member.
-
#union(*names, &block) ⇒ Class
Declares a nested union member.
Constructor Details
#initialize(type, opt = {}) ⇒ Builder
Returns a new instance of Builder.
299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/carray/struct_builder.rb', line 299 def initialize (type, opt = {}) if not opt[:pack].nil? and not opt[:pack].is_a?(Integer) raise CAStruct::DefinitionError, "invalid :pack value #{opt[:pack].inspect} (expected nil or Integer)" end @type = type ### :struct or :union @align = opt[:pack] ### nil for alignment, int for pack(n) @members = [] ### array of CArray::Struct::Builder::Member @offset = 0 ### offset of each member and size of struct @align_max = 1 ### maximum of alignment among members @size = opt[:size] ### user defined struct size @bit_offset = 0 ### 0..7, sub-byte position for bitfield accumulator end |
Class Method Details
.build_bitfield_dispatcher(name, opts, data_size) ⇒ Object
-- per-kind dispatcher builders -------------------------------------
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/carray/struct_builder.rb', line 205 def self.build_bitfield_dispatcher (name, opts, data_size) bits = opts[:bits] bit_offset = opts[:bit_offset] start_byte = bit_offset / 8 bit_in_byte = bit_offset % 8 span = (bit_offset + bits + 7) / 8 - start_byte view_bytes = case when span <= 1 then 1 when span <= 2 then 2 when span <= 4 then 4 else 8 end if start_byte + view_bytes > data_size raise CAStruct::DefinitionError, "bit member #{name.inspect} (bit_offset=#{bit_offset}, " \ "bits=#{bits}) needs a #{view_bytes}-byte view starting " \ "at byte #{start_byte}, but the record is only " \ "#{data_size} bytes — deferred to A.3+ (multi-byte bit " \ "members spanning unaligned record tails)" end vtype = {1 => :uint8, 2 => :uint16, 4 => :uint32, 8 => :uint64}[view_bytes] range = bit_in_byte..(bit_in_byte + bits - 1) reader = ->(data) { data.field(start_byte, vtype).bitfield(range)[0] } writer = ->(data, val) { data.field(start_byte, vtype).bitfield(range)[0] = val } [reader, writer].freeze end |
.build_dispatch_table(member_table, data_size) ⇒ Object
Build the DISPATCH_TABLE for a finalized MEMBER_TABLE.
Returns { name => [reader_proc, writer_proc] } where each Proc
closes over the per-member offset / opts. Called once, at the
bottom of define. Public so it can be tested in isolation.
data_size is the struct's DATA_SIZE; needed up front so we can
bounds-check bit-field projections at definition time rather than
at first access.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/carray/struct_builder.rb', line 52 def self.build_dispatch_table (member_table, data_size) dispatch = {} member_table.each do |name, entry| offset, type, opts = *entry case when type == :bitfield dispatch[name] = build_bitfield_dispatcher(name, opts, data_size) when type.is_a?(Class) dispatch[name] = build_nested_struct_dispatcher(offset, type) when type.is_a?(CArray) dispatch[name] = build_carray_template_dispatcher(offset, type) else dispatch[name] = build_primitive_dispatcher(offset, type, opts) end end dispatch end |
.build_fast_bitfield_entry(opts, data_size) ⇒ Object
Bit-field FAST entry, mirroring build_bitfield_dispatcher's geometry calculation but emitting the C-friendly tuple [FAST_KIND_BITFIELD, start_byte, view_bytes, bit_in_word, bits]. Returns nil if the field can't be fast-pathed (e.g. its spanning word reaches past DATA_SIZE -- the existing build_bitfield_dispatcher raises in that case at definition time; here we just bow out and let DISPATCH_TABLE raise).
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/carray/struct_builder.rb', line 142 def self.build_fast_bitfield_entry (opts, data_size) bits = opts[:bits] bit_offset = opts[:bit_offset] start_byte = bit_offset / 8 bit_in_word = bit_offset % 8 span = (bit_offset + bits + 7) / 8 - start_byte view_bytes = case when span <= 1 then 1 when span <= 2 then 2 when span <= 4 then 4 else 8 end return nil if data_size && start_byte + view_bytes > data_size [FAST_KIND_BITFIELD, start_byte, view_bytes, bit_in_word, bits].freeze end |
.endian_needs_swap?(endian_sym, host_endian) ⇒ Boolean
True iff a member tagged with the given endian: keyword needs
an actual byte swap on the current host. Matches CArray#endian's
short-circuit logic: :preserve and :native are always identity;
:big / :little are identity when the host already matches.
162 163 164 165 166 167 168 169 |
# File 'lib/carray/struct_builder.rb', line 162 def self.endian_needs_swap? (endian_sym, host_endian) case endian_sym when :preserve, :native then false when :big then host_endian != CA_BIG_ENDIAN when :little then host_endian != CA_LITTLE_ENDIAN else false # unknown sym — be conservative, defer to DISPATCH_TABLE end end |
.validate_endian!(opt, typename, allow) ⇒ Object
Validate the endian: option on a typed-member declaration.
allow is false for non-numeric types (e.g. fixlen) where a
byte-swap view would mangle the value.
642 643 644 645 646 647 648 649 650 651 652 653 |
# File 'lib/carray/struct_builder.rb', line 642 def self.validate_endian! (opt, typename, allow) return unless opt.key?(:endian) unless allow raise CAStruct::DefinitionError, "endian: is not supported for #{typename} members" end unless VALID_ENDIAN.include?(opt[:endian]) raise CAStruct::DefinitionError, "endian: must be one of #{VALID_ENDIAN.inspect} " \ "(got #{opt[:endian].inspect})" end end |
Instance Method Details
#array(*names, type:) ⇒ void
586 587 588 589 590 591 592 593 594 595 |
# File 'lib/carray/struct_builder.rb', line 586 def array (*args) opt = args.last.is_a?(Hash) ? args.pop : {} if not opt[:type] or not opt[:type].kind_of?(CArray) raise CAStruct::DefinitionError, "no :type given for array member (expected a CArray template)" end args.each do |arg| member(opt[:type], arg) end end |
#bit(name, bits:) ⇒ void
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
# File 'lib/carray/struct_builder.rb', line 607 def bit (name, bits:) if @type == :union raise CAStruct::DefinitionError, "bit members in :union are not supported" end if name.nil? raise CAStruct::DefinitionError, "bit member must have a name (no anonymous bit padding)" end unless bits.is_a?(Integer) && bits >= 1 && bits <= 64 raise CAStruct::DefinitionError, "bit member requires bits: as an Integer in 1..64 (got #{bits.inspect})" end total_bit_offset = @offset * 8 + @bit_offset mem = Member.new(name.to_s, :bitfield, {:bits => bits, :bit_offset => total_bit_offset, :offset => @offset}) @members.push(mem) @bit_offset += bits while @bit_offset >= 8 @offset += 1 @bit_offset -= 8 end if @align_max < 1 @align_max = 1 end end |
#flush_bit_offset ⇒ Object
Round up the byte cursor if the bitfield accumulator is mid-byte. Called before placing a non-bit member so the byte member starts on a clean byte boundary (and after the body is fully built so DATA_SIZE includes the trailing bits' byte).
317 318 319 320 321 322 |
# File 'lib/carray/struct_builder.rb', line 317 def flush_bit_offset if @bit_offset > 0 @offset += 1 @bit_offset = 0 end end |
#member(data_type, id = nil, opt = {}) ⇒ Member
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 |
# File 'lib/carray/struct_builder.rb', line 493 def member (data_type, id = nil, opt = {}) opt = opt.clone # If a bit member sequence was in flight, round up to a clean byte # boundary before placing this byte-typed member. flush_bit_offset if id id = id.to_s else id = "#{@members.size}" end case @type when :struct ### struct case @align when nil ### -- aligned @offset = alignment(@offset, data_type, opt) opt[:offset] = @offset else ### -- packed if opt[:offset] ### ---- explicit offset @offset = pack(opt[:offset], @align, opt) else opt[:offset] = @offset ### ---- auto offset end end mem = Member.new(id, data_type, opt) @members.push(mem) @offset += mem.byte_length when :union ### union alignment(0, data_type, opt) opt[:offset] = 0 mem = Member.new(id, data_type, opt) @members.push(mem) if mem.byte_length > @offset @offset = mem.byte_length end end end |
#struct(*names, &block) ⇒ Class
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/carray/struct_builder.rb', line 538 def struct (*args, &block) opt = args.last.is_a?(Hash) ? args.pop : {} if block opt = {:pack => @align}.update(opt) st = self.class.new(:struct, opt).define(&block) elsif opt[:type] and opt[:type] <= CAStruct st = opt[:type] else raise CAStruct::DefinitionError, "no type given for nested struct member (pass a block or :type)" end args.each do |arg| member(st, arg) end return st end |
#union(*names, &block) ⇒ Class
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 |
# File 'lib/carray/struct_builder.rb', line 563 def union (*args, &block) opt = args.last.is_a?(Hash) ? args.pop : {} if block opt = {:pack => @align}.update(opt) st = self.class.new(:union, opt).define(&block) elsif opt[:type] and opt[:type] <= CAStruct st = opt[:type] else raise CAStruct::DefinitionError, "no type given for nested union member (pass a block or :type)" end args.each do |arg| member(st, arg) end return st end |