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.
- data/.gitignore +8 -0
- data/History.txt +25 -0
- data/README.rdoc +99 -0
- data/Rakefile +42 -0
- data/bin/ffi-gen +7 -0
- data/examples/Rakefile +4 -0
- data/examples/interfaces/libc.i +26 -0
- data/examples/interfaces/wiiuse.i +672 -0
- data/lib/ffi-swig-generator.rb +48 -0
- data/lib/generator/application.rb +69 -0
- data/lib/generator/generator.rb +344 -0
- data/lib/generator/generatortask.rb +45 -0
- data/spec/ffi-swig-generator_spec.rb +7 -0
- data/spec/generator/generator_spec.rb +248 -0
- data/spec/generator/swig/constants.i +3 -0
- data/spec/generator/swig/enums.i +5 -0
- data/spec/generator/swig/functions.i +24 -0
- data/spec/generator/swig/structs.i +25 -0
- data/spec/generator/swig/testlib.i +48 -0
- data/spec/generator/swig/typedefs.i +5 -0
- data/spec/generator/swig/types.i +52 -0
- data/spec/generator/swig/unions.i +6 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +27 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +50 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +300 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- metadata +110 -0
@@ -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
|
+
|