Class: CAStruct::Builder

Inherits:
Object
  • Object
show all
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_size cannot 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.

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

Instance Method Summary collapse

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.

Returns:

  • (Boolean)


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

This method returns an undefined value.

Declares one or more members holding a CArray template.

Parameters:

  • names (Array<Symbol>)

    member names.

Raises:



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

This method returns an undefined value.

Declares a bitfield member. Bits accumulate within the current byte; when a byte member follows, the cursor rounds up to the next byte boundary. Only supported in :struct (not :union) and always requires a name.

Parameters:

  • name (Symbol, String)

    member name.

  • bits (Integer)

    bit width in 1..64.

Raises:



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_offsetObject

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

Declares a member with the given storage data_type. Used internally by the typed DSL methods (int32, float64, ...); call directly to place a custom template or nested type.

Parameters:

  • data_type (Object)

    data type (Symbol, Integer, CArray template, or nested struct class).

  • id (Symbol, String, nil) (defaults to: nil)

    member name; auto-generated when nil.

  • opt (Hash) (defaults to: {})

    member options (:offset, :bytes, :endian, ...).

Returns:



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

Declares a nested struct member. With a block, defines an inline nested struct; otherwise requires type: naming an existing CAStruct subclass.

Parameters:

  • names (Array<Symbol>)

    member names.

Returns:

  • (Class)

    the nested struct class.

Raises:



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

Declares a nested union member. With a block, defines an inline nested union; otherwise requires type: naming an existing CAUnion subclass.

Parameters:

  • names (Array<Symbol>)

    member names.

Returns:

  • (Class)

    the nested union class.

Raises:



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