Class: Snow::CStruct::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/snow-data/c_struct/builder.rb

Overview

Utility classes used by CStruct to create member info structs. Exposed to the user via instance_exec blocks for declaring struct/union members.

See CStruct::new, CStruct::struct, or CStruct::union.

Defined Under Namespace

Classes: MemberStackLevel

Constant Summary collapse

@@defined_types =

A Set of symbols for all types whose declaration methods have already been defined by ::flush_type_methods!.

Set.new

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(is_union: false, &block) ⇒ Builder

call-seq:

new => Builder
new { ... } => Builder

In either form, a new Builder is allocated and returned. If a block is given, then it will be instance_exec’d, allowing you to easily call any declaration methods on the builder instance.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/snow-data/c_struct/builder.rb', line 42

def initialize(is_union: false, &block)
  @member_names = Set.new
  @level = MemberStackLevel[!!is_union, 0, 1, 0, []]
  instance_exec(&block) if block_given?
  @members = []
  self.class.adjust_level(@level)
  @level.members.each { |member| member.freeze }
  @members = self.class.flatten_level(@level)
  @members.each(&:freeze)
  @members.freeze
end

Class Method Details

.adjust_level(level, start_at: 0) ⇒ Object

Iterates over a level’s members, including sub-levels, and adjusts their offsets and the level’s size accordingly. This is essentially the processing phase done to give members their offsets and levels their sizes.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/snow-data/c_struct/builder.rb', line 76

def self.adjust_level(level, start_at: 0)
  base_offset = offset = ::Snow::Memory.align_size(start_at, level.alignment)
  level.size = 0
  level.members.each do |m|
    m.offset = offset = ::Snow::Memory.align_size(offset, m.alignment)
    if m.kind_of?(MemberStackLevel)
      adjust_level(m, start_at: offset)
    end
    if level.is_union
      level.size = [level.size, (m.offset + m.size) - base_offset].max
    else
      level.size = (m.offset + m.size) - base_offset
      offset += m.size
    end
  end
end

.flatten_level(level, info_buffer = []) ⇒ Object

Flattens a MemberStackLevel’s members into a single array and returns an array of StructMemberInfo objects.



59
60
61
62
63
64
65
66
67
68
# File 'lib/snow-data/c_struct/builder.rb', line 59

def self.flatten_level(level, info_buffer = [])
  level.members.each { |m|
    if m.kind_of?(MemberStackLevel)
      flatten_level(m, info_buffer)
    else
      info_buffer.push(m)
    end
  }
  info_buffer
end

.flush_type_methods!Object

call-seq:

flush_type_methods! => self

Defines methods for declaring members of any recognized CStruct type, including aliases.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/snow-data/c_struct/builder.rb', line 112

def self.flush_type_methods!
  ::Snow::CStruct::SIZES.each do |type_name, type_size|
    next if @@defined_types.include?(type_name)

    method_name = type_name.to_s
    first_char = method_name[0]
    case
    when first_char >= ?A && first_char <= ?Z
      method_name[0] = first_char.downcase
    when first_char >= ?0 && first_char <= ?9
      method_name[0] = "_#{first_char}"
    end
    method_name = method_name.to_sym

    __send__(:define_method, method_name) do | name, lengths = 1, align: nil |
      level = instance_variable_get(:@level)

      member_names = instance_variable_get(:@member_names)

      name = name.to_sym
      raise ArgumentError, "#{name} redefined in struct" if member_names.include?(name)
      member_names.add(name)

      length = case lengths
               when Integer, Fixnum then lengths
               when Array then lengths.reduce(:*)
               else lengths.to_i
               end
      raise "Invalid length for member #{name}: must be >= 1" if length < 1

      size = length * type_size

      align = (align || ::Snow::CStruct::ALIGNMENTS[type_name]).to_i
      raise "Nil alignment for type #{type_name}" if align.nil?

      base_offset = level.offset
      offset = ::Snow::Memory.align_size(base_offset, align)
      level.offset = offset + size unless level.is_union

      member_info = ::Snow::CStruct::StructMemberInfo[
        name, type_name, size, length, align, offset]

      level.alignment = [level.alignment, align].max
      if level.is_union
        level.size = [level.size, size].max
      else
        level.size += (offset - base_offset) + size
      end

      level.members.push(member_info)
    end # define_method(type_name)

    @@defined_types.add(type_name)

  end # SIZES.each

  ::Snow::CStruct::TYPE_ALIASES.each { |short, long|
    __send__(:alias_method, short, long)
  }

  self
end

Instance Method Details

#__do_level__(align: nil, is_union: false, &block) ⇒ Object

Creates a new member level for the builder and instance_exec-s the block, then passes its alignment onto its ancestor level.



180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/snow-data/c_struct/builder.rb', line 180

def __do_level__(align: nil, is_union: false, &block)
  parent = @level
  next_level = MemberStackLevel[!!is_union, 0, 1, 0, []]
  @level = next_level

  self.instance_exec(&block)

  next_level.alignment = align || next_level.alignment
  parent.alignment = [parent.alignment, next_level.alignment].max

  @level = parent
  parent.members.push(next_level)
end

#member_infoObject

call-seq:

member_info => [StructMemberInfo]

Returns the StructMemberInfo array for the builder.



100
101
102
# File 'lib/snow-data/c_struct/builder.rb', line 100

def member_info
  @members
end

#struct(align: nil, &block) ⇒ Object

call-seq:

struct { ... }
struct(align: nil) { ... }

For the scope of the block, any members declared are considered struct members, as opposed to union members.

Each member of a struct occupies its own space inside the struct, unlike a union (where each member occupies either the same or adjacent space in the union).

Unless an alignment is specified, the default alignment of a struct is that of the largest alignment of all its members. If specifying an alignment, keep in mind that the members of the struct may need to also be manually aligned, otherwise the first member may be preceeded by padding bytes regardless of the start of the struct. See #union for more information on alignment oddities.



240
241
242
# File 'lib/snow-data/c_struct/builder.rb', line 240

def struct(align: nil, &block)
  __do_level__(align: align, is_union: false, &block)
end

#union(align: nil, &block) ⇒ Object

call-seq:

union { ... }
union(align: nil) { ... }

For the scope of the block, any members declared are considered union members, as opposed to struct members. Each member of a union occupies the same space as other members of the union, though their offsets may differ if their alignments differ as well.

If no alignment is specified for the union, its base offset will be aligned to that of the member with the largest alignment. Otherwise, if an alignment is specified, members may not occupy the same offsets relative to the beginning of the union.

For example, if a union with an alignment of 4 has uint32_t and uint64_t members with default alignments with a starting offset of 4, the uint32_t member will be located at offset 4, while the uint64_t member will be at offset 8. As such, it’s best to leave union alignments at their default unless absolutely necessary.



216
217
218
# File 'lib/snow-data/c_struct/builder.rb', line 216

def union(align: nil, &block)
  __do_level__(align: align, is_union: true, &block)
end