# Struct does some trickery with custom allocators so we can't # subclass it without writing C. Instead we define a CStruct class # that does something similar enough for our purpose. It is # subclassed just like any other class. A nice side-effect of this # syntax is that it is always clear that a CStruct is just a class and # instances of the struct are objects. # # Some light metaprogramming is used to make the following syntax possible: # # class MachHeader < CStruct # uint :magic # int :cputype # int :cpusubtype # ... # int :flags # end # # Inheritance works as you would expect. # # class LoadCommand < CStruct # uint32 :cmd # uint32 :cmdsize # end # # # inherits cmd and cmdsize as the first 2 fields # class SegmentCommand < LoadCommand # string :segname, 16 # uint32 :vmaddr # uint32 # end # # Nothing tricky or confusing there. Members of a CStruct class are # declared in the class definition. A different definition using a # more static approach probably wouldn't be very hard... if # performance is critical ... but then why are you using Ruby? ;-) # # # TODO support bit fields # # Bit fields should be supported by passing the number of bits a field # should occupy. Perhaps we could use the size 'pack' for the rest of # the field. # # class RelocationInfo < CStruct # int32 :address # uint32 :symbolnum, 24 # pack :pcrel, 1 # pack :length, 2 # pack :extern, 1 # pack :type, 4 # end class CStruct ################### # Class Constants # ################### # Size in bytes. SizeMap = { :int8 => 1, :uint8 => 1, :int16 => 2, :uint16 => 2, :int32 => 4, :uint32 => 4, :string => lambda { |*opts| opts.first }, # first opt is size # the last 3 are to make the language more C-like :int => 4, :uint => 4, :char => 1 } # 32-bit PackMap = { :int8 => 'c', :uint8 => 'C', :int16 => 's', :uint16 => 'S', :int32 => 'i', :uint32 => 'I', :string => lambda do |str, *opts| len = opts.first str.ljust(len, "\0")[0, len] end, # a few C-like names :int => 'i', :uint => 'I', :char => 'C' } # Only needed when unpacking is different from packing, i.e. strings w/ lambdas in PackMap. UnpackMap = { :string => lambda do |str, *opts| len = opts.first val = str[0, len-1].sub(/\0*$/, '') str.slice!((len-1)..-1) val end } ########################## # Class Instance Methods # ########################## # Note: const_get and const_set are used so the constants are bound # at runtime, to the real class that has subclassed CStruct. # I figured Ruby would do this but I haven't looked at the # implementation of constants so it might be tricky. # # All of this could probably be avoided with Ruby 1.9 and # private class variables. That is definitely something to # experiment with. class <