ffi_gen 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -8,13 +8,14 @@ ffi_gen - A generator for Ruby FFI bindings
8
8
 
9
9
  Features
10
10
  --------
11
- * Generation of FFI methods, structures, enums and callbacks
11
+ * Generation of FFI methods, structures, unions, enumerations and callbacks
12
12
  * Generation of YARD documentation comments
13
13
  * Tested with headers of the following libraries:
14
14
  * Clang
15
15
  * LLVM
16
16
  * OpenGL
17
17
  * SQLite3
18
+ * Cairo
18
19
 
19
20
 
20
21
  Requirements
@@ -39,10 +40,12 @@ Use the following interface in a script or Rake task:
39
40
  cflags: `llvm-config --cflags`.split(" "),
40
41
  prefixes: ["clang_", "CX"],
41
42
  blacklist: ["clang_getExpansionLocation"],
42
- output: "clang.rb"
43
+ output: "Clang/index.rb"
43
44
  )
44
45
 
45
- Output: [clang.rb](https://github.com/neelance/ffi_gen/blob/master/lib/ffi_gen/clang.rb)
46
+ Output: [Clang/index.rb](https://github.com/neelance/ffi_gen/blob/master/test/gen/Clang/index.rb)
47
+
48
+ Other generated files can be found in the [test/gen](https://github.com/neelance/ffi_gen/tree/master/test/gen) directory.
46
49
 
47
50
 
48
51
  Hints
@@ -67,7 +70,6 @@ Roadmap
67
70
  -------
68
71
 
69
72
  * Support for more libraries:
70
- * Cairo
71
73
  * (Write me if you have a whish)
72
74
  * Automatic generation of object oriented wrappers
73
75
  * Polish YARD documentation comments some more
@@ -1,8 +1,8 @@
1
1
  class FFIGen
2
- RUBY_KEYWORDS = %w{alias allocate and begin break case class def defined do else elsif end ensure false for if in initialize module next nil not or redo rescue retry return self super then true undef unless until when while yield}
3
-
4
2
  require "ffi_gen/clang"
5
-
3
+ require "ffi_gen/ruby_output"
4
+ require "ffi_gen/java_output"
5
+
6
6
  class << Clang
7
7
  def get_children(declaration)
8
8
  children = []
@@ -21,6 +21,15 @@ class FFIGen
21
21
  get_spelling_location location, file_ptr, line_ptr, column_ptr, offset_ptr
22
22
  { file: file_ptr.read_pointer, line: line_ptr.read_uint, column: column_ptr.read_uint, offset: offset_ptr.read_uint }
23
23
  end
24
+
25
+ def get_tokens(translation_unit, range)
26
+ tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
27
+ num_tokens_ptr = FFI::MemoryPointer.new :uint
28
+ Clang.tokenize translation_unit, range, tokens_ptr_ptr, num_tokens_ptr
29
+ num_tokens = num_tokens_ptr.read_uint
30
+ tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer
31
+ num_tokens.times.map { |i| Clang::Token.new tokens_ptr[i] }
32
+ end
24
33
  end
25
34
 
26
35
  class Clang::String
@@ -35,7 +44,36 @@ class FFIGen
35
44
  end
36
45
  end
37
46
 
47
+ class Clang::Cursor
48
+ def ==(other)
49
+ other.is_a?(Clang::Cursor) && Clang.equal_cursors(self, other) == 1
50
+ end
51
+
52
+ def eql?(other)
53
+ self == other
54
+ end
55
+
56
+ def hash
57
+ Clang.hash_cursor self
58
+ end
59
+ end
60
+
61
+ class Clang::Type
62
+ def ==(other)
63
+ other.is_a?(Clang::Type) && Clang.equal_types(self, other) == 1
64
+ end
65
+
66
+ def eql?(other)
67
+ self == other
68
+ end
69
+
70
+ def hash
71
+ 0 # no hash available
72
+ end
73
+ end
74
+
38
75
  class Enum
76
+ attr_accessor :name
39
77
  attr_reader :constants, :comment
40
78
 
41
79
  def initialize(generator, name, comment)
@@ -45,161 +83,41 @@ class FFIGen
45
83
  @constants = []
46
84
  end
47
85
 
48
- def write(writer)
49
- prefix_length = 0
50
- suffix_length = 0
51
-
52
- unless @constants.size < 2
53
- search_pattern = @constants.all? { |constant| constant[:name].include? "_" } ? /(?<=_)/ : /[A-Z]/
54
- first_name = @constants.first[:name]
55
-
56
- loop do
57
- position = first_name.index(search_pattern, prefix_length + 1) or break
58
- prefix = first_name[0...position]
59
- break if not @constants.all? { |constant| constant[:name].start_with? prefix }
60
- prefix_length = position
61
- end
62
-
63
- loop do
64
- position = first_name.rindex(search_pattern, first_name.size - suffix_length - 1) or break
65
- prefix = first_name[position..-1]
66
- break if not @constants.all? { |constant| constant[:name].end_with? prefix }
67
- suffix_length = first_name.size - position
68
- end
69
- end
70
-
71
- @constants.each do |constant|
72
- constant[:symbol] = ":#{@generator.to_ruby_lowercase constant[:name][prefix_length..(-1 - suffix_length)]}"
73
- end
74
-
75
- writer.comment do
76
- writer.write_description @comment
77
- writer.puts "", "<em>This entry is only for documentation and no real method. The FFI::Enum can be accessed via #enum_type(:#{ruby_name}).</em>"
78
- writer.puts "", "=== Options:"
79
- @constants.each do |constant|
80
- writer.puts "#{constant[:symbol]} ::"
81
- writer.write_description constant[:comment], false, " ", " "
82
- end
83
- writer.puts "", "@method _enum_#{ruby_name}_", "@return [Symbol]", "@scope class"
84
- end
85
-
86
- writer.puts "enum :#{ruby_name}, ["
87
- writer.indent do
88
- writer.write_array @constants, "," do |constant|
89
- "#{constant[:symbol]}#{constant[:value] ? ", #{constant[:value]}" : ''}"
90
- end
91
- end
92
- writer.puts "]", ""
93
- end
94
-
95
- def ruby_name
96
- @ruby_name ||= @generator.to_ruby_lowercase @name
86
+ def shorten_names
87
+ return if @constants.size < 2
88
+ names = @constants.map { |constant| constant[:name].parts }
89
+ names.each(&:shift) while names.map(&:first).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.first.downcase
90
+ names.each(&:pop) while names.map(&:last).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.last.downcase
97
91
  end
98
92
  end
99
93
 
100
- class Struct
101
- attr_reader :fields, :comment, :written
94
+ class StructOrUnion
95
+ attr_accessor :name, :comment
96
+ attr_reader :fields, :oo_functions, :written
102
97
 
103
- def initialize(generator, name, comment)
98
+ def initialize(generator, name, is_union)
104
99
  @generator = generator
105
100
  @name = name
106
- @comment = comment
101
+ @is_union = is_union
102
+ @comment = ""
107
103
  @fields = []
104
+ @oo_functions = []
108
105
  @written = false
109
106
  end
110
-
111
- def write(writer)
112
- @fields.each do |field|
113
- field[:symbol] = ":#{@generator.to_ruby_lowercase field[:name]}"
114
- field[:type_data] = @generator.map_type field[:type]
115
- end
116
-
117
- writer.comment do
118
- writer.write_description @comment
119
- unless @fields.empty?
120
- writer.puts "", "= Fields:"
121
- @fields.each do |field|
122
- writer.puts "#{field[:symbol]} ::"
123
- writer.write_description field[:comment], false, " (#{field[:type_data][:description]}) ", " "
124
- end
125
- end
126
- end
127
-
128
- writer.puts "class #{ruby_name} < FFI::Struct"
129
- writer.indent do
130
- writer.write_array @fields, ",", "layout ", " " do |field|
131
- "#{field[:symbol]}, #{field[:type_data][:ffi_type]}"
132
- end
133
- end
134
- writer.puts "end", ""
135
-
136
- @written = true
137
- end
138
-
139
- def ruby_name
140
- @ruby_name ||= @generator.to_ruby_camelcase @name
141
- end
142
107
  end
143
108
 
144
- class Function
109
+ class FunctionOrCallback
145
110
  attr_reader :name, :parameters, :comment
146
111
  attr_accessor :return_type
147
112
 
148
- def initialize(generator, name, is_callback, comment)
113
+ def initialize(generator, name, is_callback, blocking, comment)
149
114
  @generator = generator
150
115
  @name = name
151
116
  @parameters = []
152
117
  @is_callback = is_callback
118
+ @blocking = blocking
153
119
  @comment = comment
154
120
  end
155
-
156
- def write(writer)
157
- @parameters.each do |parameter|
158
- parameter[:type_data] = @generator.map_type parameter[:type]
159
- parameter[:ruby_name] = !parameter[:name].empty? ? @generator.to_ruby_lowercase(parameter[:name]) : parameter[:type_data][:parameter_name]
160
- parameter[:description] = []
161
- end
162
- return_type_data = @generator.map_type @return_type
163
-
164
- function_description = []
165
- return_value_description = []
166
- current_description = function_description
167
- @comment.split("\n").map do |line|
168
- line = writer.prepare_comment_line line
169
- if line.gsub! /\\param (.*?) /, ''
170
- parameter = @parameters.find { |parameter| parameter[:name] == $1 }
171
- if parameter
172
- current_description = parameter[:description]
173
- else
174
- current_description << "#{$1}: "
175
- end
176
- end
177
- current_description = return_value_description if line.gsub! '\\returns ', ''
178
- current_description << line
179
- end
180
-
181
- writer.comment do
182
- writer.write_description function_description
183
- writer.puts "", "<em>This entry is only for documentation and no real method.</em>" if @is_callback
184
- writer.puts "", "@method #{@is_callback ? "_callback_#{ruby_name}_" : ruby_name}(#{@parameters.map{ |parameter| parameter[:ruby_name] }.join(', ')})"
185
- @parameters.each do |parameter|
186
- writer.write_description parameter[:description], false, "@param [#{parameter[:type_data][:description]}] #{parameter[:ruby_name]} ", " "
187
- end
188
- writer.write_description return_value_description, false, "@return [#{return_type_data[:description]}] ", " "
189
- writer.puts "@scope class"
190
- end
191
-
192
- ffi_signature = "[#{@parameters.map{ |parameter| parameter[:type_data][:ffi_type] }.join(', ')}], #{return_type_data[:ffi_type]}"
193
- if @is_callback
194
- writer.puts "callback :#{ruby_name}, #{ffi_signature}", ""
195
- else
196
- writer.puts "attach_function :#{ruby_name}, :#{@name}, #{ffi_signature}", ""
197
- end
198
- end
199
-
200
- def ruby_name
201
- @ruby_name ||= @generator.to_ruby_lowercase @name, true
202
- end
203
121
  end
204
122
 
205
123
  class Constant
@@ -208,34 +126,36 @@ class FFIGen
208
126
  @name = name
209
127
  @value = value
210
128
  end
211
-
212
- def write(writer)
213
- writer.puts "#{@generator.to_ruby_lowercase(@name, true).upcase} = #{@value}", ""
214
- end
215
129
  end
216
130
 
217
131
  class Writer
218
132
  attr_reader :output
219
133
 
220
- def initialize
221
- @indentation = ""
134
+ def initialize(indentation_prefix, comment_prefix, comment_start = nil, comment_end = nil)
135
+ @indentation_prefix = indentation_prefix
136
+ @comment_prefix = comment_prefix
137
+ @comment_start = comment_start
138
+ @comment_end = comment_end
139
+ @current_indentation = ""
222
140
  @output = ""
223
141
  end
224
142
 
225
- def indent(prefix = " ")
226
- previous_indentation = @indentation
227
- @indentation += prefix
143
+ def indent(prefix = @indentation_prefix)
144
+ previous_indentation = @current_indentation
145
+ @current_indentation += prefix
228
146
  yield
229
- @indentation = previous_indentation
147
+ @current_indentation = previous_indentation
230
148
  end
231
149
 
232
150
  def comment(&block)
233
- indent "# ", &block
151
+ self.puts @comment_start unless @comment_start.nil?
152
+ self.indent @comment_prefix, &block
153
+ self.puts @comment_end unless @comment_end.nil?
234
154
  end
235
155
 
236
156
  def puts(*lines)
237
157
  lines.each do |line|
238
- @output << "#{@indentation}#{line}\n"
158
+ @output << "#{@current_indentation}#{line}\n"
239
159
  end
240
160
  end
241
161
 
@@ -248,11 +168,11 @@ class FFIGen
248
168
 
249
169
  def prepare_comment_line(line)
250
170
  line = line.dup
251
- line.sub! /\ ?\*+\/\s*$/, ''
252
- line.sub! /^\s*\/?\*+ ?/, ''
253
- line.gsub! /\\(brief|determine) /, ''
254
- line.gsub! '[', '('
255
- line.gsub! ']', ')'
171
+ line.sub!(/\ ?\*+\/\s*$/, '')
172
+ line.sub!(/^\s*\/?\*+ ?/, '')
173
+ line.gsub!(/\\(brief|determine) /, '')
174
+ line.gsub!('[', '(')
175
+ line.gsub!(']', ')')
256
176
  line
257
177
  end
258
178
 
@@ -260,48 +180,67 @@ class FFIGen
260
180
  if description.is_a? String
261
181
  description = description.split("\n").map { |line| prepare_comment_line(line) }
262
182
  end
263
-
183
+
264
184
  description.shift while not description.empty? and description.first.strip.empty?
265
185
  description.pop while not description.empty? and description.last.strip.empty?
186
+ description.map! { |line| line.gsub "\t", " " }
187
+ space_prefix_length = description.map{ |line| line.index(/\S/) }.compact.min
188
+ description.map! { |line| line[space_prefix_length..-1] }
266
189
  description << (not_documented_message ? "(Not documented)" : "") if description.empty?
267
190
 
268
191
  write_array description, "", first_line_prefix, other_lines_prefix
269
192
  end
270
193
  end
271
194
 
272
- attr_reader :ruby_module, :ffi_lib, :headers, :output, :blacklist, :cflags
195
+ class Name
196
+ attr_reader :raw, :parts
197
+
198
+ def initialize(generator, raw)
199
+ @generator = generator
200
+ @raw = raw
201
+ @parts = @raw.is_a?(Array) ? raw : @raw.sub(/^(#{generator.prefixes.join('|')})/, '').split(/_|(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).reject(&:empty?)
202
+ end
203
+
204
+ def format(*modes, keyword_blacklist)
205
+ parts = @parts.dup
206
+ parts.map!(&:downcase) if modes.include? :downcase
207
+ parts.map!(&:upcase) if modes.include? :upcase
208
+ parts.map! { |s| s[0].upcase + s[1..-1] } if modes.include? :camelcase
209
+ parts[0] = parts[0][0].downcase + parts[0][1..-1] if modes.include? :initial_downcase
210
+ str = parts.join(modes.include?(:underscores) ? "_" : "")
211
+ str.sub!(/^\d/, '_\0') # fix illegal beginnings
212
+ str = "#{str}_" if keyword_blacklist.include? str
213
+ str
214
+ end
215
+
216
+ def empty?
217
+ @parts.empty?
218
+ end
219
+ end
220
+
221
+ attr_reader :module_name, :ffi_lib, :headers, :prefixes, :output, :cflags
273
222
 
274
223
  def initialize(options = {})
275
- @ruby_module = options[:ruby_module] or fail "No module name given."
276
- @ffi_lib = options[:ffi_lib] or fail "No FFI library given."
277
- @headers = options[:headers] or fail "No headers given."
278
- @cflags = options.fetch :cflags, []
279
- @prefixes = options.fetch :prefixes, []
280
- @blacklist = options.fetch :blacklist, []
281
- @output = options.fetch :output, $stdout
282
-
283
- blacklist = @blacklist
284
- @blacklist = lambda { |name| blacklist.include? name } if @blacklist.is_a? Array
224
+ @module_name = options[:module_name] or fail "No module name given."
225
+ @ffi_lib = options[:ffi_lib] or fail "No FFI library given."
226
+ @headers = options[:headers] or fail "No headers given."
227
+ @cflags = options.fetch :cflags, []
228
+ @prefixes = options.fetch :prefixes, []
229
+ @blocking = options.fetch :blocking, []
230
+ @ffi_lib_flags = options.fetch :ffi_lib_flags, nil
231
+ @output = options.fetch :output, $stdout
285
232
 
286
233
  @translation_unit = nil
287
234
  @declarations = nil
288
235
  end
289
236
 
290
237
  def generate
291
- writer = Writer.new
292
- writer.puts "# Generated by ffi_gen. Please do not change this file by hand.", "", "require 'ffi'", "", "module #{@ruby_module}"
293
- writer.indent do
294
- writer.puts "extend FFI::Library", "ffi_lib '#{@ffi_lib}'", ""
295
- declarations.each do |name, declaration|
296
- declaration.write writer
297
- end
298
- end
299
- writer.puts "end"
238
+ code = send "generate_#{File.extname(@output)[1..-1]}"
300
239
  if @output.is_a? String
301
- File.open(@output, "w") { |file| file.write writer.output }
240
+ File.open(@output, "w") { |file| file.write code }
302
241
  puts "ffi_gen: #{@output}"
303
242
  else
304
- @output.write writer.output
243
+ @output.write code
305
244
  end
306
245
  end
307
246
 
@@ -310,7 +249,7 @@ class FFIGen
310
249
 
311
250
  args = []
312
251
  @headers.each do |header|
313
- args.push "-include", header
252
+ args.push "-include", header unless header.is_a? Regexp
314
253
  end
315
254
  args.concat @cflags
316
255
  args_ptr = FFI::MemoryPointer.new :pointer, args.size
@@ -334,7 +273,7 @@ class FFIGen
334
273
  header_files = []
335
274
  Clang.get_inclusions translation_unit, proc { |included_file, inclusion_stack, include_length, client_data|
336
275
  filename = Clang.get_file_name(included_file).to_s_and_dispose
337
- header_files << included_file if @headers.any? { |header| filename.end_with? header }
276
+ header_files << included_file if @headers.any? { |header| header.is_a?(Regexp) ? header =~ filename : filename.end_with?(header) }
338
277
  }, nil
339
278
 
340
279
  @declarations = {}
@@ -345,253 +284,191 @@ class FFIGen
345
284
 
346
285
  extent = Clang.get_cursor_extent declaration
347
286
  comment_range = Clang.get_range previous_declaration_end, Clang.get_range_start(extent)
348
- unless declaration[:kind] == :enum_decl or declaration[:kind] == :struct_decl # keep comment for typedef_decl
287
+ unless [:enum_decl, :struct_decl, :union_decl].include? declaration[:kind] # keep comment for typedef_decl
349
288
  previous_declaration_end = Clang.get_range_end extent
350
289
  end
351
290
 
352
291
  next if not header_files.include? file
353
292
 
354
- name = Clang.get_cursor_spelling(declaration).to_s_and_dispose
355
- next if @blacklist[name]
356
-
357
293
  comment = extract_comment translation_unit, comment_range
358
294
 
359
- case declaration[:kind]
360
- when :enum_decl, :struct_decl
361
- read_named_declaration declaration, name, comment unless name.empty?
362
-
363
- when :function_decl
364
- function = Function.new self, name, false, comment
365
- function.return_type = Clang.get_cursor_result_type declaration
366
- @declarations[name] = function
367
-
368
- Clang.get_children(declaration).each do |function_child|
369
- next if function_child[:kind] != :parm_decl
370
- param_name = Clang.get_cursor_spelling(function_child).to_s_and_dispose
371
- param_type = Clang.get_cursor_type function_child
372
- function.parameters << { name: param_name, type: param_type}
373
- end
374
-
375
- when :typedef_decl
376
- typedef_children = Clang.get_children declaration
377
- if typedef_children.size == 1
378
- read_named_declaration typedef_children.first, name, comment unless @declarations.has_key? name
379
-
380
- elsif typedef_children.size > 1
381
- callback = Function.new self, name, true, comment
382
- callback.return_type = Clang.get_cursor_type typedef_children.first
383
- @declarations[name] = callback
384
-
385
- typedef_children[1..-1].each do |param_decl|
386
- param_name = Clang.get_cursor_spelling(param_decl).to_s_and_dispose
387
- param_type = Clang.get_cursor_type param_decl
388
- callback.parameters << { name: param_name, type: param_type }
389
- end
390
- end
391
-
392
- when :macro_definition
393
- tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
394
- num_tokens_ptr = FFI::MemoryPointer.new :uint
395
- Clang.tokenize translation_unit, extent, tokens_ptr_ptr, num_tokens_ptr
396
- num_tokens = num_tokens_ptr.read_uint
397
- tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer
398
-
399
- if num_tokens == 3
400
- token = Clang::Token.new tokens_ptr[1]
401
- if Clang.get_token_kind(token) == :literal
402
- value = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
403
- @declarations[name] = Constant.new self, name, value
404
- end
405
- end
406
-
407
- end
295
+ read_named_declaration declaration, comment
408
296
  end
409
297
 
410
298
  @declarations
411
299
  end
412
300
 
413
- def read_named_declaration(declaration, name, comment)
414
- old_declaration = @declarations.delete name
415
- comment = "#{old_declaration.comment}\n#{comment}" if old_declaration
301
+ def read_named_declaration(declaration, comment)
302
+ name = Name.new self, Clang.get_cursor_spelling(declaration).to_s_and_dispose
416
303
 
417
304
  case declaration[:kind]
418
305
  when :enum_decl
419
306
  enum = Enum.new self, name, comment
420
- @declarations[name] = enum
307
+ @declarations[Clang.get_cursor_type(declaration)] = enum
421
308
 
422
309
  previous_constant_location = Clang.get_cursor_location declaration
310
+ next_constant_value = 0
423
311
  Clang.get_children(declaration).each do |enum_constant|
424
- constant_name = Clang.get_cursor_spelling(enum_constant).to_s_and_dispose
425
-
426
- constant_value = nil
427
- value_cursor = Clang.get_children(enum_constant).first
428
- constant_value = value_cursor && case value_cursor[:kind]
429
- when :integer_literal
430
- tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
431
- num_tokens_ptr = FFI::MemoryPointer.new :uint
432
- Clang.tokenize translation_unit, Clang.get_cursor_extent(value_cursor), tokens_ptr_ptr, num_tokens_ptr
433
- token = Clang::Token.new tokens_ptr_ptr.read_pointer
434
- literal = Clang.get_token_spelling translation_unit, token
435
- Clang.dispose_tokens translation_unit, tokens_ptr_ptr.read_pointer, num_tokens_ptr.read_uint
436
- literal
437
- else
438
- next # skip those entries for now
439
- end
312
+ constant_name = Name.new self, Clang.get_cursor_spelling(enum_constant).to_s_and_dispose
440
313
 
441
314
  constant_location = Clang.get_cursor_location enum_constant
442
315
  constant_comment_range = Clang.get_range previous_constant_location, constant_location
443
316
  constant_comment = extract_comment translation_unit, constant_comment_range
444
317
  previous_constant_location = constant_location
445
318
 
446
- enum.constants << { name: constant_name, value: constant_value, comment: constant_comment }
319
+ catch :unsupported_value do
320
+ value_cursor = Clang.get_children(enum_constant).first
321
+ constant_value = if value_cursor
322
+ read_value value_cursor
323
+ else
324
+ next_constant_value
325
+ end
326
+
327
+ enum.constants << { name: constant_name, value: constant_value, comment: constant_comment }
328
+ next_constant_value = constant_value + 1
329
+ end
447
330
  end
448
331
 
449
- when :struct_decl
450
- struct = Struct.new self, name, comment
332
+ when :struct_decl, :union_decl
333
+ struct = @declarations.delete(Clang.get_cursor_type(declaration)) || StructOrUnion.new(self, name, (declaration[:kind] == :union_decl))
334
+ raise if not struct.fields.empty?
335
+ struct.comment << "\n#{comment}"
451
336
 
452
337
  struct_children = Clang.get_children declaration
453
- previous_child_end = Clang.get_cursor_location declaration
454
- struct_children.each_with_index do |struct_child, index|
455
- child_name = Clang.get_cursor_spelling(struct_child).to_s_and_dispose
456
- child_extent = Clang.get_cursor_extent struct_child
338
+ previous_field_end = Clang.get_cursor_location declaration
339
+ until struct_children.empty?
340
+ nested_declaration = [:struct_decl, :union_decl].include?(struct_children.first[:kind]) ? struct_children.shift : nil
341
+ field = struct_children.shift
342
+ raise if field[:kind] != :field_decl
457
343
 
458
- child_comment_range = Clang.get_range previous_child_end, Clang.get_range_start(child_extent)
459
- child_comment = extract_comment translation_unit, child_comment_range
344
+ field_name = Name.new self, Clang.get_cursor_spelling(field).to_s_and_dispose
345
+ field_extent = Clang.get_cursor_extent field
346
+
347
+ field_comment_range = Clang.get_range previous_field_end, Clang.get_range_start(field_extent)
348
+ field_comment = extract_comment translation_unit, field_comment_range
460
349
 
461
350
  # check for comment starting on same line
462
- next_child_start = index < struct_children.size - 1 ? Clang.get_cursor_location(struct_children[index + 1]) : Clang.get_range_end(Clang.get_cursor_extent(declaration))
463
- following_comment_range = Clang.get_range Clang.get_range_end(child_extent), next_child_start
351
+ next_field_start = struct_children.first ? Clang.get_cursor_location(struct_children.first) : Clang.get_range_end(Clang.get_cursor_extent(declaration))
352
+ following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start
464
353
  following_comment_token = extract_comment translation_unit, following_comment_range, false, false
465
- if following_comment_token and Clang.get_spelling_location_data(Clang.get_token_location(translation_unit, following_comment_token))[:line] == Clang.get_spelling_location_data(Clang.get_range_end(child_extent))[:line]
466
- child_comment = Clang.get_token_spelling(translation_unit, following_comment_token).to_s_and_dispose
467
- previous_child_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
354
+ if following_comment_token and Clang.get_spelling_location_data(Clang.get_token_location(translation_unit, following_comment_token))[:line] == Clang.get_spelling_location_data(Clang.get_range_end(field_extent))[:line]
355
+ field_comment = Clang.get_token_spelling(translation_unit, following_comment_token).to_s_and_dispose
356
+ previous_field_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
468
357
  else
469
- previous_child_end = Clang.get_range_end child_extent
358
+ previous_field_end = Clang.get_range_end field_extent
470
359
  end
471
360
 
472
- case struct_child[:kind]
473
- when :field_decl
474
- field_type = Clang.get_cursor_type struct_child
475
-
476
- struct.fields << { name: child_name, type: field_type, comment: child_comment }
477
- when :struct_decl
478
- read_named_declaration struct_child, child_name, child_comment
361
+ if nested_declaration
362
+ read_named_declaration nested_declaration, ""
363
+ decl = @declarations[Clang.get_cursor_type(nested_declaration)]
364
+ decl.name = Name.new(self, name.parts + field_name.parts) if decl and decl.name.empty?
479
365
  end
366
+
367
+ field_type = Clang.get_cursor_type field
368
+ struct.fields << { name: field_name, type: field_type, comment: field_comment }
480
369
  end
481
370
 
482
- @declarations[name] = struct
371
+ @declarations[Clang.get_cursor_type(declaration)] = struct
372
+
373
+ when :function_decl
374
+ function = FunctionOrCallback.new self, name, false, @blocking.include?(name.raw), comment
375
+ function.return_type = Clang.get_cursor_result_type declaration
376
+ @declarations[declaration] = function
377
+
378
+ Clang.get_children(declaration).each do |function_child|
379
+ next if function_child[:kind] != :parm_decl
380
+ param_name = Name.new self, Clang.get_cursor_spelling(function_child).to_s_and_dispose
381
+ param_type = Clang.get_cursor_type function_child
382
+ tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
383
+ is_array = tokens.any? { |t| Clang.get_token_spelling(translation_unit, t).to_s_and_dispose == "[" }
384
+ function.parameters << { name: param_name, type: param_type, is_array: is_array }
385
+ end
483
386
 
484
- end
485
- end
486
-
487
- def extract_comment(translation_unit, range, search_backwards = true, return_spelling = true)
488
- tokens_ptr_ptr = FFI::MemoryPointer.new :pointer
489
- num_tokens_ptr = FFI::MemoryPointer.new :uint
490
- Clang.tokenize translation_unit, range, tokens_ptr_ptr, num_tokens_ptr
491
- num_tokens = num_tokens_ptr.read_uint
492
- tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer
493
- indices = search_backwards ? (num_tokens - 1).downto(0) : 0.upto(num_tokens - 1)
494
- indices.each do |i|
495
- token = Clang::Token.new tokens_ptr[i]
496
- if Clang.get_token_kind(token) == :comment
497
- return return_spelling ? Clang.get_token_spelling(translation_unit, token).to_s_and_dispose : token
387
+ pointee_declaration = function.parameters.first && get_pointee_declaration(function.parameters.first[:type])
388
+ if pointee_declaration
389
+ type_prefix = pointee_declaration.name.parts.join.downcase
390
+ function_name_parts = name.parts.dup
391
+ while type_prefix.start_with? function_name_parts.first.downcase
392
+ type_prefix = type_prefix[function_name_parts.first.size..-1]
393
+ function_name_parts.shift
394
+ end
395
+ if type_prefix.empty?
396
+ pointee_declaration.oo_functions << [Name.new(self, function_name_parts), function, get_pointee_declaration(function.return_type)]
397
+ end
498
398
  end
499
- end
500
- ""
501
- end
502
-
503
- def map_type(full_type)
504
- name = Clang.get_cursor_spelling(Clang.get_type_declaration(full_type)).to_s_and_dispose
505
- declaration = !name.empty? && @declarations[name]
506
399
 
507
- canonical_type = Clang.get_canonical_type full_type
508
- data_array = case canonical_type[:kind]
509
- when :void then [":void", "nil"]
510
- when :bool then [":bool", "Boolean"]
511
- when :u_char then [":uchar", "Integer"]
512
- when :u_short then [":ushort", "Integer"]
513
- when :u_int then [":uint", "Integer"]
514
- when :u_long then [":ulong", "Integer"]
515
- when :u_long_long then [":ulong_long", "Integer"]
516
- when :char_s, :s_char then [":char", "Integer"]
517
- when :short then [":short", "Integer"]
518
- when :int then [":int", "Integer"]
519
- when :long then [":long", "Integer"]
520
- when :long_long then [":long_long", "Integer"]
521
- when :float then [":float", "Float"]
522
- when :double then [":double", "Float"]
523
- when :pointer
524
- pointee_type = Clang.get_pointee_type canonical_type
525
- result = nil
526
- case pointee_type[:kind]
527
- when :char_s
528
- result = [":string", "String"]
529
- when :record
530
- pointee_name = Clang.get_cursor_spelling(Clang.get_type_declaration(pointee_type)).to_s_and_dispose
531
- pointee_declaration = !pointee_name.empty? && @declarations[pointee_name]
532
- result = [pointee_declaration.ruby_name, pointee_declaration.ruby_name] if pointee_declaration and pointee_declaration.written
533
- when :function_proto
534
- result = [":#{declaration.ruby_name}", "Proc(_callback_#{declaration.ruby_name}_)"] if declaration
400
+ when :typedef_decl
401
+ typedef_children = Clang.get_children declaration
402
+ if typedef_children.size == 1
403
+ child_declaration = @declarations[Clang.get_cursor_type(typedef_children.first)]
404
+ child_declaration.name = name if child_declaration and child_declaration.name.empty?
405
+
406
+ elsif typedef_children.size > 1
407
+ callback = FunctionOrCallback.new self, name, true, false, comment
408
+ callback.return_type = Clang.get_cursor_type typedef_children.first
409
+ @declarations[Clang.get_cursor_type(declaration)] = callback
410
+
411
+ typedef_children[1..-1].each do |param_decl|
412
+ param_name = Name.new self, Clang.get_cursor_spelling(param_decl).to_s_and_dispose
413
+ param_type = Clang.get_cursor_type param_decl
414
+ callback.parameters << { name:param_name, type: param_type }
415
+ end
416
+ end
417
+
418
+ when :macro_definition
419
+ tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(declaration)
420
+ if tokens.size == 3
421
+ if Clang.get_token_kind(tokens[1]) == :literal
422
+ value = Clang.get_token_spelling(translation_unit, tokens[1]).to_s_and_dispose
423
+ value.sub!(/[A-Za-z]+$/, '') unless value.start_with? '0x' # remove number suffixes
424
+ @declarations[name] ||= Constant.new self, name, value
425
+ end
535
426
  end
536
427
 
537
- if result.nil?
538
- pointer_depth = 0
539
- pointer_target_name = ""
540
- current_type = full_type
541
- loop do
542
- declaration = Clang.get_type_declaration current_type
543
- pointer_target_name = to_ruby_camelcase Clang.get_cursor_spelling(declaration).to_s_and_dispose
544
- break if not pointer_target_name.empty?
545
-
546
- case current_type[:kind]
547
- when :pointer
548
- pointer_depth += 1
549
- current_type = Clang.get_pointee_type current_type
550
- when :unexposed
551
- break
552
- else
553
- pointer_target_name = Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose
554
- break
555
- end
428
+ end
429
+ end
430
+
431
+ def read_value(cursor)
432
+ parts = []
433
+ tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(cursor)
434
+ tokens.each do |token|
435
+ spelling = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
436
+ case Clang.get_token_kind(token)
437
+ when :literal
438
+ parts << spelling
439
+ when :punctuation
440
+ case spelling
441
+ when ",", "}"
442
+ # ignored
443
+ when "+", "-", "<<", ">>"
444
+ parts << spelling
445
+ else
446
+ throw :unsupported_value
556
447
  end
557
- result = [":pointer", "FFI::Pointer(#{'*' * pointer_depth}#{pointer_target_name})", pointer_target_name]
448
+ else
449
+ throw :unsupported_value
558
450
  end
559
-
560
- result
561
- when :record
562
- ["#{declaration.ruby_name}.by_value", declaration.ruby_name]
563
- when :enum
564
- [":#{declaration.ruby_name}", "Symbol from _enum_#{declaration.ruby_name}_", declaration.ruby_name]
565
- when :constant_array
566
- element_type_data = map_type Clang.get_array_element_type(canonical_type)
567
- size = Clang.get_array_size canonical_type
568
- ["[#{element_type_data[:ffi_type]}, #{size}]", "Array<#{element_type_data[:description]}>"]
569
- else
570
- raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}"
571
451
  end
572
-
573
- { ffi_type: data_array[0], description: data_array[1], parameter_name: to_ruby_lowercase(data_array[2] || data_array[1]) }
452
+ eval parts.join
574
453
  end
575
454
 
576
- def to_ruby_lowercase(str, avoid_keywords = false)
577
- str = str.dup
578
- str.sub! /^(#{@prefixes.join('|')})/, '' # remove prefixes
579
- str.gsub! /([A-Z][a-z])/, '_\1' # add underscores before word beginnings
580
- str.gsub! /([a-z])([A-Z])/, '\1_\2' # add underscores after word endings
581
- str.sub! /^_*/, '' # remove underscores at the beginning
582
- str.gsub! /__+/, '_' # replace multiple underscores by only one
583
- str.downcase!
584
- str.sub! /^\d/, '_\0' # fix illegal beginnings
585
- str = "_#{str}" if avoid_keywords and RUBY_KEYWORDS.include? str
586
- str
455
+ def get_pointee_declaration(type)
456
+ canonical_type = Clang.get_canonical_type type
457
+ return nil if canonical_type[:kind] != :pointer
458
+ pointee_type = Clang.get_pointee_type canonical_type
459
+ return nil if pointee_type[:kind] != :record
460
+ @declarations[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
587
461
  end
588
462
 
589
- def to_ruby_camelcase(str)
590
- str = str.dup
591
- str.sub! /^(#{@prefixes.join('|')})/, '' # remove prefixes
592
- str.gsub!(/(^|_)[a-z]/) { |match| match.upcase } # make word beginnings upcased
593
- str.gsub! '_', '' # remove all underscores
594
- str
463
+ def extract_comment(translation_unit, range, search_backwards = true, return_spelling = true)
464
+ tokens = Clang.get_tokens translation_unit, range
465
+ iterator = search_backwards ? tokens.reverse_each : tokens.each
466
+ iterator.each do |token|
467
+ if Clang.get_token_kind(token) == :comment
468
+ return return_spelling ? Clang.get_token_spelling(translation_unit, token).to_s_and_dispose : token
469
+ end
470
+ end
471
+ ""
595
472
  end
596
473
 
597
474
  def self.generate(options = {})
@@ -602,12 +479,11 @@ end
602
479
 
603
480
  if __FILE__ == $0
604
481
  FFIGen.generate(
605
- ruby_module: "FFIGen::Clang",
482
+ module_name: "FFIGen::Clang",
606
483
  ffi_lib: "clang",
607
484
  headers: ["clang-c/Index.h"],
608
485
  cflags: `llvm-config --cflags`.split(" "),
609
486
  prefixes: ["clang_", "CX"],
610
- blacklist: ["clang_getExpansionLocation"],
611
487
  output: File.join(File.dirname(__FILE__), "ffi_gen/clang.rb")
612
488
  )
613
489
  end