remogatto-ffi-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,49 @@
1
+
2
+ module FFI
3
+ module Generator
4
+ # :stopdoc:
5
+ VERSION = '0.1.0'
6
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
7
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
8
+ # :startdoc:
9
+
10
+ # Returns the version string for the library.
11
+ #
12
+ def self.version
13
+ VERSION
14
+ end
15
+
16
+ # Returns the library path for the module. If any arguments are given,
17
+ # they will be joined to the end of the libray path using
18
+ # <tt>File.join</tt>.
19
+ #
20
+ def self.libpath( *args )
21
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
22
+ end
23
+
24
+ # Returns the lpath for the module. If any arguments are given,
25
+ # they will be joined to the end of the path using
26
+ # <tt>File.join</tt>.
27
+ #
28
+ def self.path( *args )
29
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
30
+ end
31
+
32
+ # Utility method used to require all files ending in .rb that lie in the
33
+ # directory below this file that has the same name as the filename passed
34
+ # in. Optionally, a specific _directory_ name can be passed in such that
35
+ # the _filename_ does not have to be equivalent to the directory.
36
+ #
37
+ def self.require_all_libs_relative_to( fname, dir = nil )
38
+ dir ||= ::File.basename(fname, '.*')
39
+ search_me = ::File.expand_path(
40
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
41
+
42
+ Dir.glob(search_me).sort.each {|rb| require rb}
43
+ end
44
+ end
45
+ end
46
+
47
+ FFI::Generator.require_all_libs_relative_to(__FILE__, 'generator')
48
+
49
+ # 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-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,304 @@
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(node, indent = 0)
47
+ @node = node
48
+ @indent = ' ' * indent
49
+ @symname = get_attr('name')
50
+ end
51
+ def get_attr(name)
52
+ attr = (@node / "./attributelist/attribute[@name='#{name}']").first
53
+ attr['value'] if attr
54
+ end
55
+ end
56
+ class Type < Node
57
+ def initialize(node, indent = 0)
58
+ super
59
+ @type = get_attr('type')
60
+ @decl = get_attr('decl')
61
+ @statement = @type.to_s + @decl.to_s
62
+ end
63
+ def to_s
64
+ get_type
65
+ end
66
+ private
67
+ def is_native?
68
+ Generator::TYPES.has_key?(@type)
69
+ end
70
+ def is_pointer?
71
+ @decl and @decl[/(\)\.p|^p)\./]
72
+ end
73
+ def is_enum?
74
+ @type[/^enum/]
75
+ end
76
+ def is_array?
77
+ @decl and @decl[/\w+\(\d+\)/]
78
+ end
79
+ def is_struct?
80
+ @type[/^struct/]
81
+ end
82
+ def is_union?
83
+ @type[/^union/]
84
+ end
85
+ def is_constant?
86
+ @type[/^q\(const\)/]
87
+ end
88
+ def native
89
+ if is_native?
90
+ @type = Generator::TYPES[@type]
91
+ get_type
92
+ end
93
+ end
94
+ def constant
95
+ if is_constant?
96
+ @type = @type.scan(/^q\(const\)\.(.+)/).flatten[0]
97
+ get_type
98
+ end
99
+ end
100
+ def pointer
101
+ if is_pointer?
102
+ if @type[/char/]
103
+ @type = ':string'
104
+ @decl.gsub!(/p./, '')
105
+ get_type
106
+ else
107
+ return ':pointer'
108
+ end
109
+ end
110
+ end
111
+ def array
112
+ if is_array?
113
+ num = @decl.scan(/\w+\((\d+)\)/).flatten[0]
114
+ @decl.gsub!(/\w+\(\d+\)/, '')
115
+ "[#{get_type}, #{num}]"
116
+ end
117
+ end
118
+ def struct
119
+ if is_struct?
120
+ @type = Structure.camelcase(@type.scan(/^struct\s(\w+)/).flatten[0])
121
+ get_type
122
+ end
123
+ end
124
+ def union
125
+ if is_union?
126
+ @type = Union.camelcase(@type.scan(/^union\s(\w+)/).flatten[0])
127
+ get_type
128
+ end
129
+ end
130
+ def enum
131
+ if is_enum?
132
+ @type = Generator::TYPES['int']
133
+ get_type
134
+ end
135
+ end
136
+ def typedef
137
+ if Generator.typedefs.has_key?(@type)
138
+ @type = Generator.typedefs[@type]
139
+ get_type
140
+ end
141
+ end
142
+ def get_type
143
+ constant || pointer || enum || typedef || native || struct || union || array || "#{@type}"
144
+ end
145
+ end
146
+ class Typedef < Type
147
+ attr_reader :symname, :type
148
+ def initialize(node, indent = 0)
149
+ super
150
+ @symname = get_attr('name')
151
+ @type = is_pointer? ? ':pointer' : get_attr('type')
152
+ end
153
+ end
154
+ class Constant < Node
155
+ def initialize(node, indent = 0)
156
+ super
157
+ @name, @value = get_attr('sym_name'), get_attr('value')
158
+ end
159
+ def to_s
160
+ @indent + "#{@name} = #{@value}"
161
+ end
162
+ end
163
+ class Enum < Node
164
+ def initialize(node, indent = 0)
165
+ super
166
+ eval_items
167
+ end
168
+ def to_s
169
+ @items.sort { |i1, i2| i1[1] <=> i2[1] }.inject("") do |result, item|
170
+ result << assignment_str(item[0], item[1]) << "\n"
171
+ end
172
+ end
173
+ private
174
+ def assignment_str(name, value)
175
+ @indent + "#{name} = #{value}"
176
+ end
177
+ def eval_expr(expr)
178
+ if expr.include?('+')
179
+ (@items[expr[/\w+/]].to_i + 1).to_s
180
+ else
181
+ 0.to_s
182
+ end
183
+ end
184
+ def eval_items
185
+ @items = {}
186
+ get_items.each do |i|
187
+ node = Node.new(i)
188
+ @items[node.get_attr('name')] = node.get_attr('enumvalueex') ? eval_expr(node.get_attr('enumvalueex')) : node.get_attr('enumvalue')
189
+ end
190
+ @items
191
+ end
192
+ def get_items
193
+ @node / './enumitem'
194
+ end
195
+ end
196
+ class Structure < Node
197
+ def self.camelcase(name)
198
+ name.gsub(/^\w|\_\w/).each {|c| c.upcase }.delete('_')
199
+ end
200
+ def initialize(node, indent = 0)
201
+ super
202
+ @name = self.class.camelcase(@symname)
203
+ end
204
+ def to_s
205
+ fields_str = fields.inject("") do |str, f|
206
+ str << @indent + ' ' * 9 << f.join(', ') << ",\n"
207
+ end
208
+ code = klass_string + @indent + " layout(\n" + fields_str.chomp.chomp(',') + "\n" + @indent + " )\n" + @indent + "end\n"
209
+ end
210
+ private
211
+ def klass_string
212
+ @indent + "class #{@name} < FFI::Struct\n"
213
+ end
214
+ def fields
215
+ (@node / 'cdecl').inject([]) do |array, field|
216
+ array << [":#{Node.new(field).symname}", "#{Type.new(field)}"]
217
+ end
218
+ end
219
+ end
220
+ class Union < Structure
221
+ private
222
+ def klass_string
223
+ @indent + "class #{@name} < FFI::Union\n"
224
+ end
225
+ end
226
+ class Function < Node
227
+ class Argument < Type
228
+ def initialize(node, indent = 0)
229
+ super
230
+ @decl = @type
231
+ end
232
+ end
233
+ def to_s
234
+ params = get_params(@node).inject([]) do |array, node|
235
+ array << Argument.new(node).to_s
236
+ end.collect { |p| "#{p}" }
237
+ @indent + "attach_function :#{@symname}, [ #{params.join(', ')} ], #{Type.new(@node).to_s}"
238
+ end
239
+ private
240
+ def get_params(node)
241
+ parmlist = node / './attributelist/parmlist/parm'
242
+ end
243
+ end
244
+ class Parser
245
+ @indent = 2
246
+ class << self
247
+ def get_verbatim(node)
248
+ node.xpath("./attributelist/attribute[@name='code']").first['value']
249
+ end
250
+ def is_insert_runtime?(node)
251
+ section = node.xpath("./attributelist/attribute[@name='section']")
252
+ section.first['value'] == 'runtime' if section.first
253
+ end
254
+ def is_constant?(node)
255
+ node.name == 'constant'
256
+ end
257
+ def is_enum?(node)
258
+ node.name == 'enum'
259
+ end
260
+ def is_function_decl?(node)
261
+ node.name == 'cdecl' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'function'
262
+ end
263
+ def is_struct?(node)
264
+ node.name == 'class' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'struct'
265
+ end
266
+ def is_union?(node)
267
+ node.name == 'class' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'union'
268
+ end
269
+ def is_typedef?(node)
270
+ node.name == 'cdecl' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'typedef'
271
+ end
272
+ def generate(node)
273
+ result = ""
274
+ node.traverse do |node|
275
+ if is_constant?(node)
276
+ result << Constant.new(node, @indent).to_s << "\n"
277
+ elsif is_typedef?(node)
278
+ typedef = Typedef.new(node)
279
+ Generator.add_type(typedef.symname, typedef.type)
280
+ elsif is_enum?(node)
281
+ e = Enum.new(node, @indent)
282
+ Generator.add_type(e.symname, Generator::TYPES['int'])
283
+ result << e.to_s << "\n"
284
+ elsif is_struct?(node)
285
+ s = Structure.new(node, @indent)
286
+ Generator.add_type(s.symname, "struct #{s.symname}")
287
+ result << s.to_s
288
+ elsif is_union?(node)
289
+ s = Union.new(node, @indent)
290
+ Generator.add_type(s.symname, "union #{s.symname}")
291
+ result << s.to_s
292
+ elsif is_function_decl?(node)
293
+ result << Function.new(node, @indent).to_s << "\n"
294
+ elsif node.name == 'insert' and not is_insert_runtime?(node)
295
+ result << get_verbatim(node)
296
+ end
297
+ end
298
+ result
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
304
+
@@ -0,0 +1,45 @@
1
+ require 'rake/tasklib'
2
+
3
+ module FFI
4
+ module Generator
5
+ class Task < Rake::TaskLib
6
+ def initialize(options = {})
7
+ @options = { :input_fn => '*.i', :output_dir => 'generated/' }.merge(options)
8
+ namespace 'ffi' do
9
+ define_generate_task
10
+ define_clean_task
11
+ end
12
+ end
13
+ private
14
+ def define_file_task(fn, xml_fn, output_fn)
15
+ desc "Generate #{output_fn} from #{fn}"
16
+ file output_fn => fn do
17
+ mkdir_p @options[:output_dir], :verbose => false
18
+ puts "Generating #{xml_fn} from #{fn} using SWIG..."
19
+ `swig -xml #{xml_fn} #{fn}`
20
+ puts "Generating #{output_fn} from #{xml_fn}..."
21
+ File.open(output_fn, 'w') do |file|
22
+ file << Parser.generate(Nokogiri::XML(File.open(xml_fn)))
23
+ end
24
+ end
25
+ end
26
+ def define_file_tasks
27
+ Dir.glob(@options[:input_fn]).inject([]) do |output_fns, fn|
28
+ output_fn = File.join(@options[:output_dir], "#{File.basename(fn, '.i')}_wrap.rb")
29
+ xml_fn = File.join(@options[:output_dir], "#{File.basename(fn, '.i')}_wrap.xml")
30
+ define_file_task(fn, xml_fn, output_fn)
31
+ output_fns << output_fn
32
+ end
33
+ end
34
+ def define_generate_task
35
+ (task :generate => define_file_tasks).add_description('Generate all files')
36
+ end
37
+ def define_clean_task
38
+ desc 'Remove all generated files'
39
+ task :clean do
40
+ rm_rf @options[:output_dir] unless @options[:output_dir] == '.'
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ describe FFI::Generator do
5
+ end
6
+
7
+ # EOF