remogatto-ffi-swig-generator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ module FFI
2
+ module Generator
3
+ # :stopdoc:
4
+ VERSION = '0.2.1'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args )
20
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ end
22
+
23
+ # Returns the lpath for the module. If any arguments are given,
24
+ # they will be joined to the end of the path using
25
+ # <tt>File.join</tt>.
26
+ #
27
+ def self.path( *args )
28
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
29
+ end
30
+
31
+ # Utility method used to require all files ending in .rb that lie in the
32
+ # directory below this file that has the same name as the filename passed
33
+ # in. Optionally, a specific _directory_ name can be passed in such that
34
+ # the _filename_ does not have to be equivalent to the directory.
35
+ #
36
+ def self.require_all_libs_relative_to( fname, dir = nil )
37
+ dir ||= ::File.basename(fname, '.*')
38
+ search_me = ::File.expand_path(
39
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
40
+
41
+ Dir.glob(search_me).sort.each {|rb| require rb}
42
+ end
43
+ end
44
+ end
45
+
46
+ FFI::Generator.require_all_libs_relative_to(__FILE__, 'generator')
47
+
48
+ # EOF
@@ -0,0 +1,69 @@
1
+ require 'getoptlong'
2
+
3
+ module FFI
4
+ module Generator
5
+ class Application
6
+ OPTIONS = [
7
+ [ "--help", "-u", GetoptLong::NO_ARGUMENT,
8
+ "Display help information." ],
9
+ [ "--version", "-v", GetoptLong::NO_ARGUMENT,
10
+ "Display the version number and quit." ],
11
+ ]
12
+
13
+ USAGE_PREAMBLE = <<-EOU
14
+ Usage: ffi-gen [options] <input_file> <output_file>
15
+
16
+ <input_file> is the xml parse tree generated by swig -xml command.
17
+ <output_file> is the ruby-ffi wrapper file.
18
+
19
+ EOU
20
+ class << self
21
+ def run
22
+ process_args
23
+ if ARGV.size == 2
24
+ File.open(ARGV[1], 'w') do |file|
25
+ file << FFI::Generator::Parser.generate(Nokogiri::XML(File.open(ARGV[0])))
26
+ end
27
+ else
28
+ help
29
+ raise "Invalid number of arguments!"
30
+ end
31
+ end
32
+ private
33
+ def do_option(option, value = nil)
34
+ case option
35
+ when '--help'
36
+ help
37
+ exit
38
+ when '--version'
39
+ puts "ffi-swig-generator, version #{Generator::VERSION}\n"
40
+ exit
41
+ end
42
+ end
43
+ def command_line_options
44
+ OPTIONS.collect { |lst| lst[0..-2] }
45
+ end
46
+ def process_args
47
+ opts = GetoptLong.new(*command_line_options)
48
+ opts.each { |opt, value| do_option(opt, value) }
49
+ end
50
+ def help
51
+ puts
52
+ puts USAGE_PREAMBLE
53
+ puts "Recognized options are:"
54
+ puts
55
+ OPTIONS.sort.each do |long, short, mode, desc|
56
+ if mode == GetoptLong::REQUIRED_ARGUMENT
57
+ if desc =~ /\b([A-Z]{2,})\b/
58
+ long = long + "=#{$1}"
59
+ end
60
+ end
61
+ printf " %-20s (%s)\n", long, short
62
+ printf " %s\n", desc
63
+ puts
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,344 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+
4
+ module FFI
5
+ module Generator
6
+ @typedefs = {}
7
+ TYPES = {
8
+ 'char' => ':char',
9
+ 'double' => ':double',
10
+ 'float' => ':float',
11
+ 'unsigned long' => ':ulong',
12
+ 'unsigned char' => ':uchar',
13
+ 'signed char' => ':char',
14
+ 'unsigned char' => ':uchar',
15
+ 'short' => ':short',
16
+ 'signed short' => ':short',
17
+ 'signed short int' => ':short',
18
+ 'unsigned short' => ':ushort',
19
+ 'unsigned short int' => ':ushort',
20
+ 'int' => ':int',
21
+ 'signed int' => ':int',
22
+ 'unsigned int' => ':uint',
23
+ 'long' => ':long',
24
+ 'long int' => ':long',
25
+ 'signed long' => ':long',
26
+ 'signed long int' => ':long',
27
+ 'unsigned long' => ':ulong',
28
+ 'unsigned long int' => ':ulong',
29
+ 'long unsigned int' => ':ulong',
30
+ 'long long' => ':long_long',
31
+ 'long long int' => ':long_long',
32
+ 'signed long long' => ':long_long',
33
+ 'signed long long int' => ':long_long',
34
+ 'unsigned long long' => ':ulong_long',
35
+ 'unsigned long long int' => ':ulong_long',
36
+ 'void' => ':void'
37
+ }
38
+ class << self
39
+ attr_reader :typedefs
40
+ def add_type(ctype, rtype)
41
+ @typedefs[ctype] = rtype
42
+ end
43
+ end
44
+ class Node
45
+ attr_reader :symname
46
+ def initialize(params = { })
47
+ params = { :indent => 0 }.merge(params)
48
+ @node, @indent = params[:node], params[:indent]
49
+ @indent_str = ' ' * @indent
50
+ @symname = get_attr('name')
51
+ end
52
+ def get_attr(name)
53
+ if @node
54
+ attr = (@node / "./attributelist/attribute[@name='#{name}']").first
55
+ attr['value'] if attr
56
+ end
57
+ end
58
+ end
59
+ class Type < Node
60
+ def initialize(params = { })
61
+ super
62
+ @statement = params[:statement] || get_statement
63
+ end
64
+ def to_s
65
+ get_type
66
+ end
67
+ private
68
+ def get_statement
69
+ get_attr('decl').to_s + get_attr('type').to_s if @node
70
+ end
71
+ def is_native?
72
+ Generator::TYPES.has_key?(@statement)
73
+ end
74
+ def is_pointer?
75
+ @statement[/^p\./] and not is_callback?
76
+ end
77
+ def is_enum?
78
+ @statement[/^enum/]
79
+ end
80
+ def is_array?
81
+ @statement and @statement[/\w+\(\d+\)/]
82
+ end
83
+ def is_struct?
84
+ @statement[/^struct/]
85
+ end
86
+ def is_union?
87
+ @statement[/^union/]
88
+ end
89
+ def is_constant?
90
+ @statement[/^q\(const\)/]
91
+ end
92
+ def is_callback?
93
+ @statement[/^p.f\(/]
94
+ end
95
+ def native
96
+ if is_native?
97
+ @statement = Generator::TYPES[@statement]
98
+ get_type
99
+ end
100
+ end
101
+ def constant
102
+ if is_constant?
103
+ @statement = @statement.scan(/^q\(const\)\.(.+)/).flatten[0]
104
+ get_type
105
+ end
106
+ end
107
+ def pointer
108
+ if is_pointer?
109
+ if @statement[/char/] and @statement.scan(/p\./).size == 1
110
+ @statement = ':string'
111
+ # @decl.gsub!(/p\./, '')
112
+ get_type
113
+ else
114
+ return ':pointer'
115
+ end
116
+ end
117
+ end
118
+ def array
119
+ if is_array?
120
+ num = @statement.scan(/\w+\((\d+)\)/).flatten[0]
121
+ @statement.gsub!(/\w+\(\d+\)\./, '')
122
+ "[#{get_type}, #{num}]"
123
+ end
124
+ end
125
+ def struct
126
+ if is_struct?
127
+ @statement = Structure.camelcase(@statement.scan(/^struct\s(\w+)/).flatten[0])
128
+ get_type
129
+ end
130
+ end
131
+ def union
132
+ if is_union?
133
+ @statement = Union.camelcase(@statement.scan(/^union\s(\w+)/).flatten[0])
134
+ get_type
135
+ end
136
+ end
137
+ def enum
138
+ if is_enum?
139
+ @statement = Generator::TYPES['int']
140
+ get_type
141
+ end
142
+ end
143
+ def callback
144
+ Callback.new(:node => @node).to_s if is_callback?
145
+ end
146
+ def typedef
147
+ if Generator.typedefs.has_key?(@statement)
148
+ @statement = Generator.typedefs[@statement]
149
+ get_type
150
+ end
151
+ end
152
+ def get_type
153
+ constant || pointer || enum || typedef || native || struct || union || array || callback || "#{@statement}"
154
+ end
155
+ end
156
+ class Typedef < Type
157
+ attr_reader :symname, :statement
158
+ def initialize(params = { })
159
+ super
160
+ @symname = get_attr('name')
161
+ # @type = is_pointer? ? ':pointer' : get_attr('type')
162
+ # p @statement
163
+ end
164
+ end
165
+ class Constant < Node
166
+ def initialize(params = { })
167
+ super
168
+ @name, @value = get_attr('sym_name'), get_attr('value')
169
+ end
170
+ def to_s
171
+ @indent_str + "#{@name} = #{@value}"
172
+ end
173
+ end
174
+ class Enum < Node
175
+ def initialize(params = { })
176
+ super
177
+ eval_items
178
+ end
179
+ def to_s
180
+ @items.sort { |i1, i2| i1[1] <=> i2[1] }.inject("") do |result, item|
181
+ result << assignment_str(item[0], item[1]) << "\n"
182
+ end
183
+ end
184
+ private
185
+ def assignment_str(name, value)
186
+ @indent_str + "#{name} = #{value}"
187
+ end
188
+ def eval_expr(expr)
189
+ if expr.include?('+')
190
+ (@items[expr[/\w+/]].to_i + 1).to_s
191
+ else
192
+ 0.to_s
193
+ end
194
+ end
195
+ def eval_items
196
+ @items = {}
197
+ get_items.each do |i|
198
+ node = Node.new(:node => i)
199
+ @items[node.get_attr('name')] = node.get_attr('enumvalueex') ? eval_expr(node.get_attr('enumvalueex')) : node.get_attr('enumvalue')
200
+ end
201
+ @items
202
+ end
203
+ def get_items
204
+ @node / './enumitem'
205
+ end
206
+ end
207
+ class Structure < Node
208
+ def self.camelcase(name)
209
+ name.gsub(/^\w|\_\w/).each {|c| c.upcase }.delete('_')
210
+ end
211
+ def initialize(params = { })
212
+ super
213
+ @name = self.class.camelcase(@symname)
214
+ end
215
+ def to_s
216
+ fields_str = fields.inject("") do |str, f|
217
+ str << @indent_str + ' ' * 9 << f.join(', ') << ",\n"
218
+ end
219
+ code = klass_string + @indent_str + " layout(\n" + fields_str.chomp.chomp(',') + "\n" + @indent_str + " )\n" + @indent_str + "end\n"
220
+ end
221
+ private
222
+ def klass_string
223
+ @indent_str + "class #{@name} < FFI::Struct\n"
224
+ end
225
+ def fields
226
+ (@node / 'cdecl').inject([]) do |array, field|
227
+ array << [":#{Node.new(:node => field).symname}", "#{Type.new(:node => field)}"]
228
+ end
229
+ end
230
+ end
231
+ class Union < Structure
232
+ private
233
+ def klass_string
234
+ @indent_str + "class #{@name} < FFI::Union\n"
235
+ end
236
+ end
237
+ class Function < Type
238
+ class Argument < Type
239
+ def to_s
240
+ get_attr('type') == 'void' ? nil : super
241
+ end
242
+ end
243
+ def initialize(params = { })
244
+ super
245
+ @type = get_attr('type')
246
+ end
247
+ def to_s
248
+ params = get_params(@node).inject([]) do |array, node|
249
+ array << Argument.new(:node => node).to_s
250
+ end.collect { |p| "#{p}" }
251
+ @indent_str + "attach_function :#{@symname}, [ #{params.join(', ')} ], #{get_rvalue}"
252
+ end
253
+ private
254
+ def get_params(node)
255
+ parmlist = node / './attributelist/parmlist/parm'
256
+ end
257
+ def get_rvalue
258
+ Type.new(:node => @node, :statement => @statement.scan(/^f\(.*\)\.(.+)/).flatten[0]).to_s
259
+ end
260
+ end
261
+ class Callback < Type
262
+ def to_s
263
+ params = get_params.inject([]) do |array, type|
264
+ array << (type == 'void' ? '' : Type.new(:statement => type).to_s)
265
+ end
266
+ @indent_str + "callback(:#{@symname}, [ #{params.join(', ')} ], #{get_rtype})"
267
+ end
268
+ private
269
+ def get_params
270
+ @statement.scan(/p.f\((.*)\)/).flatten[0].split(',')
271
+ end
272
+ def get_rtype
273
+ Type.new(:statement => @statement.scan(/\)\.(\w+)/).flatten[0]).to_s
274
+ end
275
+ end
276
+ class Parser
277
+ @indent = 2
278
+ class << self
279
+ def get_verbatim(node)
280
+ node.xpath("./attributelist/attribute[@name='code']").first['value']
281
+ end
282
+ def is_insert_runtime?(node)
283
+ section = node.xpath("./attributelist/attribute[@name='section']")
284
+ section.first['value'] == 'runtime' if section.first
285
+ end
286
+ def is_constant?(node)
287
+ node.name == 'constant'
288
+ end
289
+ def is_enum?(node)
290
+ node.name == 'enum'
291
+ end
292
+ def is_function_decl?(node)
293
+ node.name == 'cdecl' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'function'
294
+ end
295
+ def is_struct?(node)
296
+ node.name == 'class' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'struct'
297
+ end
298
+ def is_union?(node)
299
+ node.name == 'class' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'union'
300
+ end
301
+ def is_typedef?(node)
302
+ node.name == 'cdecl' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'typedef'
303
+ end
304
+ def is_callback?(node)
305
+ (node / "./attributelist/attribute[@name='decl']").first['value'] =~ /^p\.f\(/
306
+ end
307
+ def generate(node)
308
+ result = ""
309
+ node.traverse do |node|
310
+ if is_constant?(node)
311
+ result << Constant.new(:node => node, :indent => @indent).to_s << "\n"
312
+ elsif is_typedef?(node)
313
+ typedef = Typedef.new(:node => node)
314
+ Generator.add_type(typedef.symname, typedef.statement)
315
+ if is_callback?(node)
316
+ cb = Callback.new(:node => node, :indent => @indent).to_s << "\n"
317
+ Generator.add_type(typedef.symname, ":#{typedef.symname}")
318
+ result << cb.to_s
319
+ end
320
+ elsif is_enum?(node)
321
+ e = Enum.new(:node => node, :indent => @indent)
322
+ Generator.add_type(e.symname, Generator::TYPES['int'])
323
+ result << e.to_s << "\n"
324
+ elsif is_struct?(node)
325
+ s = Structure.new(:node => node, :indent => @indent)
326
+ Generator.add_type(s.symname, "struct #{s.symname}")
327
+ result << s.to_s
328
+ elsif is_union?(node)
329
+ s = Union.new(:node => node, :indent => @indent)
330
+ Generator.add_type(s.symname, "union #{s.symname}")
331
+ result << s.to_s
332
+ elsif is_function_decl?(node)
333
+ result << Function.new(:node => node, :indent => @indent).to_s << "\n"
334
+ elsif node.name == 'insert' and not is_insert_runtime?(node)
335
+ result << get_verbatim(node)
336
+ end
337
+ end
338
+ result
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+