1require 'fiddle'
2require 'fiddle/value'
3require 'fiddle/pack'
4
5module Fiddle
6  # C struct shell
7  class CStruct
8    # accessor to Fiddle::CStructEntity
9    def CStruct.entity_class
10      CStructEntity
11    end
12  end
13
14  # C union shell
15  class CUnion
16    # accessor to Fiddle::CUnionEntity
17    def CUnion.entity_class
18      CUnionEntity
19    end
20  end
21
22  # Used to construct C classes (CUnion, CStruct, etc)
23  #
24  # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
25  # easy-to-use manner.
26  module CStructBuilder
27    # Construct a new class given a C:
28    # * class +klass+ (CUnion, CStruct, or other that provide an
29    #   #entity_class)
30    # * +types+ (Fiddle::TYPE_INT, Fiddle::TYPE_SIZE_T, etc., see the C types
31    #   constants)
32    # * corresponding +members+
33    #
34    # Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
35    # easy-to-use manner.
36    #
37    # Example:
38    #
39    #   require 'fiddle/struct'
40    #   require 'fiddle/cparser'
41    #
42    #   include Fiddle::CParser
43    #
44    #   types, members = parse_struct_signature(['int i','char c'])
45    #
46    #   MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
47    #
48    #   obj = MyStruct.allocate
49    #
50    def create(klass, types, members)
51      new_class = Class.new(klass){
52        define_method(:initialize){|addr|
53          @entity = klass.entity_class.new(addr, types)
54          @entity.assign_names(members)
55        }
56        define_method(:to_ptr){ @entity }
57        define_method(:to_i){ @entity.to_i }
58        members.each{|name|
59          define_method(name){ @entity[name] }
60          define_method(name + "="){|val| @entity[name] = val }
61        }
62      }
63      size = klass.entity_class.size(types)
64      new_class.module_eval(<<-EOS, __FILE__, __LINE__+1)
65        def new_class.size()
66          #{size}
67        end
68        def new_class.malloc()
69          addr = Fiddle.malloc(#{size})
70          new(addr)
71        end
72      EOS
73      return new_class
74    end
75    module_function :create
76  end
77
78  # A C struct wrapper
79  class CStructEntity < Fiddle::Pointer
80    include PackInfo
81    include ValueUtil
82
83    # Allocates a C struct with the +types+ provided.
84    #
85    # When the instance is garbage collected, the C function +func+ is called.
86    def CStructEntity.malloc(types, func = nil)
87      addr = Fiddle.malloc(CStructEntity.size(types))
88      CStructEntity.new(addr, types, func)
89    end
90
91    # Returns the offset for the packed sizes for the given +types+.
92    #
93    #   Fiddle::CStructEntity.size(
94    #     [ Fiddle::TYPE_DOUBLE,
95    #       Fiddle::TYPE_INT,
96    #       Fiddle::TYPE_CHAR,
97    #       Fiddle::TYPE_VOIDP ]) #=> 24
98    def CStructEntity.size(types)
99      offset = 0
100
101      max_align = types.map { |type, count = 1|
102        last_offset = offset
103
104        align = PackInfo::ALIGN_MAP[type]
105        offset = PackInfo.align(last_offset, align) +
106                 (PackInfo::SIZE_MAP[type] * count)
107
108        align
109      }.max
110
111      PackInfo.align(offset, max_align)
112    end
113
114    # Wraps the C pointer +addr+ as a C struct with the given +types+.
115    #
116    # When the instance is garbage collected, the C function +func+ is called.
117    #
118    # See also Fiddle::Pointer.new
119    def initialize(addr, types, func = nil)
120      set_ctypes(types)
121      super(addr, @size, func)
122    end
123
124    # Set the names of the +members+ in this C struct
125    def assign_names(members)
126      @members = members
127    end
128
129    # Calculates the offsets and sizes for the given +types+ in the struct.
130    def set_ctypes(types)
131      @ctypes = types
132      @offset = []
133      offset = 0
134
135      max_align = types.map { |type, count = 1|
136        orig_offset = offset
137        align = ALIGN_MAP[type]
138        offset = PackInfo.align(orig_offset, align)
139
140        @offset << offset
141
142        offset += (SIZE_MAP[type] * count)
143
144        align
145      }.max
146
147      @size = PackInfo.align(offset, max_align)
148    end
149
150    # Fetch struct member +name+
151    def [](name)
152      idx = @members.index(name)
153      if( idx.nil? )
154        raise(ArgumentError, "no such member: #{name}")
155      end
156      ty = @ctypes[idx]
157      if( ty.is_a?(Array) )
158        r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
159      else
160        r = super(@offset[idx], SIZE_MAP[ty.abs])
161      end
162      packer = Packer.new([ty])
163      val = packer.unpack([r])
164      case ty
165      when Array
166        case ty[0]
167        when TYPE_VOIDP
168          val = val.collect{|v| Pointer.new(v)}
169        end
170      when TYPE_VOIDP
171        val = Pointer.new(val[0])
172      else
173        val = val[0]
174      end
175      if( ty.is_a?(Integer) && (ty < 0) )
176        return unsigned_value(val, ty)
177      elsif( ty.is_a?(Array) && (ty[0] < 0) )
178        return val.collect{|v| unsigned_value(v,ty[0])}
179      else
180        return val
181      end
182    end
183
184    # Set struct member +name+, to value +val+
185    def []=(name, val)
186      idx = @members.index(name)
187      if( idx.nil? )
188        raise(ArgumentError, "no such member: #{name}")
189      end
190      ty  = @ctypes[idx]
191      packer = Packer.new([ty])
192      val = wrap_arg(val, ty, [])
193      buff = packer.pack([val].flatten())
194      super(@offset[idx], buff.size, buff)
195      if( ty.is_a?(Integer) && (ty < 0) )
196        return unsigned_value(val, ty)
197      elsif( ty.is_a?(Array) && (ty[0] < 0) )
198        return val.collect{|v| unsigned_value(v,ty[0])}
199      else
200        return val
201      end
202    end
203
204    def to_s() # :nodoc:
205      super(@size)
206    end
207  end
208
209  # A C union wrapper
210  class CUnionEntity < CStructEntity
211    include PackInfo
212
213    # Allocates a C union the +types+ provided.
214    #
215    # When the instance is garbage collected, the C function +func+ is called.
216    def CUnionEntity.malloc(types, func=nil)
217      addr = Fiddle.malloc(CUnionEntity.size(types))
218      CUnionEntity.new(addr, types, func)
219    end
220
221    # Returns the size needed for the union with the given +types+.
222    #
223    #   Fiddle::CUnionEntity.size(
224    #     [ Fiddle::TYPE_DOUBLE,
225    #       Fiddle::TYPE_INT,
226    #       Fiddle::TYPE_CHAR,
227    #       Fiddle::TYPE_VOIDP ]) #=> 8
228    def CUnionEntity.size(types)
229      types.map { |type, count = 1|
230        PackInfo::SIZE_MAP[type] * count
231      }.max
232    end
233
234    # Calculate the necessary offset and for each union member with the given
235    # +types+
236    def set_ctypes(types)
237      @ctypes = types
238      @offset = Array.new(types.length, 0)
239      @size   = self.class.size types
240    end
241  end
242end
243
244