Class: CTypes::Bitfield::Builder

Inherits:
Object
  • Object
show all
Includes:
Helpers
Defined in:
lib/ctypes/bitfield/builder.rb

Overview

CTypes::Bitfield layout builder

This class is used to describe the memory layout of a CTypes::Bitfield type. There are two approaches provided here for describing the layout, the first is a constructive interface using #unsigned, #signed, #align, and #skip, to build the bitfield from right-to-left. These methods track how many bits have been used, and automatically determine the offset of fields as they’re declared.

The second interface is the programmatic interface that can be used to generate the CTypes::Bitfield layout from data. This uses #field to explicitly declare fields using bit size, offset, and signedness.

Examples:

using the constructive interface via #layout

class MyBits < CTypes::Bitfield
  layout do
    # the body of this block is evaluated within a Builder instance
    unsigned :bit             # single bit for a at offset 0
    unsigned :two, 2          # two bits for this field at offset 1
    signed :nibble, 4         # four bit nibble as a signed int, offset 3
  end
end

using the programmatic interface via #layout

class MyBits < CTypes::Bitfield
  layout do
    # the body of this block is evaluated within a Builder instance
    field :bit, size: 1, offset: 0      # single bit at offset 0
    field :two, size: 2, offset: 1      # two bits at offset 1
    field :nibble, size: 4, offset: 3   # four bits at offset 3
  end
end

construct CTypes::Bitfield programmatically

b = CTypes::Bitfield.builder        # => #<CTypes::Bitfield::Builder>
b.field(:one, bits: 1, offset: 0)   # one bit at offset 0, named :one

# Create additional fields from data loaded from elsewhere
extra_fields = [
  [:two, 2, 1],                     # two bits for this field named :two
  [:nibble, 4, 3]                   # four bits for the :nibble field
]
extra_fields.each do |name, bits, offset|
  b.field(name, bits:, offset:)
end

# build the type
t = b.build                         # => #<Bitfield ...>

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helpers

#array, #bitfield, #bitmap, #enum, #string, #struct, #union

Constructor Details

#initializeBuilder

Returns a new instance of Builder.



58
59
60
61
62
63
64
65
# File 'lib/ctypes/bitfield/builder.rb', line 58

def initialize
  @fields = []
  @schema = {}
  @default = {}
  @offset = 0
  @layout = []
  @max = 0
end

Instance Attribute Details

#offsetObject (readonly)

get the offset of the next unused bit in the bitfield



68
69
70
# File 'lib/ctypes/bitfield/builder.rb', line 68

def offset
  @offset
end

Instance Method Details

#align(bits) ⇒ Object

Note:

#align cannot be mixed with calls to #field

set the alignment of the next field declared using #signed or #unsigned

Examples:

class MyBits < CTypes::Bitfield
  layout do
    unsigned :a       # single bit at offset 0
    align 4
    unsigned :b, 2    # two bits at offset 4
    align 4
    unsigned :c       # single bit at offset 8
  end
end

Parameters:

  • bits (Integer)

    bit alignment of the next field

Raises:



136
137
138
139
140
141
142
# File 'lib/ctypes/bitfield/builder.rb', line 136

def align(bits)
  raise Error, "cannot mix `#align` and `#field` in Bitfield layout" unless
      @offset
  @offset += bits - (@offset % bits)
  @layout << "align #{bits}"
  self
end

#buildBitfield

build a CTypes::Bitfield instance with the layout configured in this builder

Returns:

  • (Bitfield)

    bitfield with the layout defined in this builder



72
73
74
75
76
# File 'lib/ctypes/bitfield/builder.rb', line 72

def build
  k = Class.new(Bitfield)
  k.send(:apply_layout, self)
  k
end

#bytes(n) ⇒ Object

set the size of the CTypes::Bitfield in bytes

Once the size is set, the Bitfield cannot grow past that size. Any calls to #signed or #unsigned that go beyond the size will raise errors.

Parameters:

  • n (Integer)

    size in bytes



195
196
197
198
199
# File 'lib/ctypes/bitfield/builder.rb', line 195

def bytes(n)
  @layout << "bytes #{n}"
  @max = n * 8
  self
end

#endian(value) ⇒ Object

set the endian for this CTypes::Bitfield

Parameters:

  • value (Symbol)

    ‘:big` or `:little`



104
105
106
107
# File 'lib/ctypes/bitfield/builder.rb', line 104

def endian(value)
  @endian = Endian[value]
  self
end

#field(name, offset:, bits:, signed: false) ⇒ Object

Note:

This method should not be used in combination with

declare a bit field at a specific offset This method is an alternative the construtive interface provided by #skip, #align, #unsigned, and #signed. This is a programmatic interface for explicitly declaring fields using offset & bitcount.

#skip, #align, #unsigned, #signed.

Parameters:

  • name (String, Symbol)

    name of the field

  • offset (Integer)

    right to left bit offset, where 0 is the least significant bit in a byte

  • bits (Integer)

    number of bits used by this bitfield

  • signed (Boolean) (defaults to: false)

    set to true to unpack as a signed integer

Raises:



215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/ctypes/bitfield/builder.rb', line 215

def field(name, offset:, bits:, signed: false)
  name = name.to_sym
  raise Error, "duplicate field: %p" % [name] if
      @fields.any? { |(n, _)| n == name }

  @layout << "field %p, offset: %d, bits: %d, signed: %p" %
    [name, offset, bits, signed]
  @offset = nil

  __field_impl(name:, offset:, bits:, signed:)
  self
end

#resultObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

get the layout description for internal use in CTypes::Bitfield



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/ctypes/bitfield/builder.rb', line 80

def result
  dry_type = Dry::Types["coercible.hash"]
    .schema(@schema)
    .strict
    .default(@default.freeze)

  type = case @max
  when 0..8
    UInt8
  when 9..16
    UInt16
  when 17..32
    UInt32
  when 32..64
    UInt64
  else
    raise Error, "bitfields greater than 64 bits not supported"
  end

  [type, @fields, dry_type, @endian, @layout]
end

#signed(name, bits = 1) ⇒ Object

append a new signed field to the bitfield

Parameters:

  • name (String, Symbol)

    name of the field

  • bits (Integer) (defaults to: 1)

    number of bits

Raises:



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/ctypes/bitfield/builder.rb', line 169

def signed(name, bits = 1)
  unless @offset
    raise Error,
      "cannot mix `#signed` and `#field` in Bitfield layout"
  end

  name = name.to_sym
  raise Error, "duplicate field: %p" % [name] if
      @fields.any? { |(n, _)| n == name }

  @layout << ((bits == 1) ?
      "signed %p" % [name] :
      "signed %p, %d" % [name, bits])

  __field_impl(name:, bits:, offset: @offset, signed: true)
  @offset += bits
  self
end

#skip(bits) ⇒ Object

skip ‘bits` bits in the layout of this bitfield

Parameters:

  • bits (Integer)

    number of bits to skip

Raises:



111
112
113
114
115
116
117
118
# File 'lib/ctypes/bitfield/builder.rb', line 111

def skip(bits)
  raise Error, "cannot mix `#skip` and `#field` in Bitfield layout" unless
      @offset
  @offset += bits
  @max = @offset if @offset > @max
  @layout << "skip #{bits}"
  self
end

#unsigned(name, bits = 1) ⇒ Object

append a new unsigned field to the bitfield

Parameters:

  • name (String, Symbol)

    name of the field

  • bits (Integer) (defaults to: 1)

    number of bits

Raises:



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ctypes/bitfield/builder.rb', line 147

def unsigned(name, bits = 1)
  unless @offset
    raise Error,
      "cannot mix `#unsigned` and `#field` in Bitfield layout"
  end

  name = name.to_sym
  raise Error, "duplicate field: %p" % [name] if
      @fields.any? { |(n, _)| n == name }

  @layout << ((bits == 1) ?
      "unsigned %p" % [name] :
      "unsigned %p, %d" % [name, bits])

  __field_impl(name:, bits:, offset: @offset, signed: false)
  @offset += bits
  self
end