ffi_gen 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4d8ac88756f647601a2ede8639d9c6534f91d778
4
+ data.tar.gz: ac9f05381eeef791a9bf41e3aef0c6887165e9ca
5
+ SHA512:
6
+ metadata.gz: e3fa0b5854c6dbc8fe228a69077af65ea67f88dee3d6a17c995988c46debc140d3d056d9a8c6e698c5183f2c4809ad78d7ca7e7267529b3ae3274d8557674e93
7
+ data.tar.gz: 2be1889882fd621d0305c6d75bf59e9eac03c95bfd40df5f69c66853ea198fbae298fa1a44a2ddb9955429b76af715182c69ec79738f717d08b8b3515bea7c7e
data/README.md CHANGED
@@ -1,73 +1,76 @@
1
- ffi_gen - A generator for Ruby FFI bindings
2
- ===========================================
1
+ # A generator for Ruby FFI bindings
3
2
 
4
- *Author:* Richard Musiol
5
- *Contributors:* Jeremy Voorhis (thanks for the initial idea)
3
+ *Author:* Richard Musiol
4
+ *Contributors:* Jeremy Voorhis (thanks for the initial idea)
6
5
  *License:* MIT (see LICENSE)
7
6
 
8
7
 
9
- Features
10
- --------
8
+ ## Features
9
+
11
10
  * Generation of FFI methods, structures, unions, enumerations and callbacks
12
11
  * Generation of YARD documentation comments
13
12
  * Tested with headers of the following libraries:
13
+ * Cairo
14
+ * CEF (Chromium Embedded Framework)
14
15
  * Clang
16
+ * LibSSH2
15
17
  * LLVM
16
18
  * OpenGL
17
19
  * SQLite3
18
- * Cairo
19
20
 
20
21
 
21
- Requirements
22
- ------------
22
+ ## Requirements
23
23
 
24
24
  * Ruby 1.9
25
- * Clang 3.0 ([Download](http://llvm.org/releases/download.html#3.0), use the binaries or configure with ``--enable-shared``)
25
+ * Clang 3.5 or later ([Download](http://llvm.org/releases/download.html#3.5) the binaries or
26
+ build from source configured with ``--enable-shared``)
26
27
 
27
28
  *These requirements are only for running the generator. The generated files are Ruby 1.8 compatible and do not need Clang.*
28
29
 
29
30
 
30
- Example
31
- -------
31
+ ## Example
32
+
32
33
  Use the following interface in a script or Rake task:
33
34
 
34
- require "ffi_gen"
35
-
36
- FFIGen.generate(
37
- ruby_module: "Clang",
38
- ffi_lib: "clang",
39
- headers: ["clang-c/Index.h"],
40
- cflags: `llvm-config --cflags`.split(" "),
41
- prefixes: ["clang_", "CX"],
42
- blacklist: ["clang_getExpansionLocation"],
43
- output: "Clang/index.rb"
44
- )
35
+ ```ruby
36
+ require "ffi_gen"
37
+
38
+ FFIGen.generate(
39
+ module_name: "Clang",
40
+ ffi_lib: "clang",
41
+ headers: ["clang-c/Index.h"],
42
+ cflags: `llvm-config --cflags`.split(" "),
43
+ prefixes: ["clang_", "CX"],
44
+ output: "clang-c/index.rb"
45
+ )
46
+ ```
45
47
 
46
- Output: [Clang/index.rb](https://github.com/neelance/ffi_gen/blob/master/test/gen/Clang/index.rb)
48
+ Output: [clang-c/index.rb](https://github.com/neelance/ffi_gen/blob/master/test/output/clang-c/Index.rb)
47
49
 
48
- Other generated files can be found in the [test/gen](https://github.com/neelance/ffi_gen/tree/master/test/gen) directory.
50
+ Other generated files can be found in the [test/output](https://github.com/neelance/ffi_gen/tree/master/test/output) directory.
49
51
 
50
52
 
51
- Hints
52
- -----
53
+ ## Hints
53
54
 
54
55
  You may need to set additional include directories:
55
56
 
56
- export CPATH=/usr/lib/gcc/x86_64-linux-gnu/4.6.1/include
57
+ ```
58
+ export CPATH=/usr/lib/gcc/x86_64-linux-gnu/4.6.1/include
59
+ ```
57
60
 
58
61
  Your GCC include paths can be seen with:
59
62
 
60
- `gcc -print-prog-name=cc1` -v
63
+ ```
64
+ `gcc -print-prog-name=cc1` -v
65
+ ```
61
66
 
62
67
 
63
- Projects using ffi_gen
64
- ----------------------
68
+ ## Projects using ffi_gen
65
69
 
66
70
  * https://github.com/jvoorhis/ruby-llvm
67
71
 
68
72
 
69
- Roadmap
70
- -------
73
+ ## Roadmap
71
74
 
72
75
  * Support for more libraries:
73
76
  * (Write me if you have a whish)
@@ -75,6 +78,6 @@ Roadmap
75
78
  * Polish YARD documentation comments some more
76
79
 
77
80
 
78
- Feedback
79
- --------
80
- Please use GitHub's issue tracker for problems or suggestions. Pull requests are welcome, too.
81
+ ## Feedback
82
+
83
+ Please use GitHub's issue tracker for problems or suggestions. Pull requests are welcome, too.
@@ -1,18 +1,18 @@
1
+ require 'ffi'
2
+
1
3
  class FFIGen
2
4
  require "ffi_gen/clang"
3
- require "ffi_gen/ruby_output"
4
- require "ffi_gen/java_output"
5
5
 
6
6
  class << Clang
7
- def get_children(declaration)
7
+ def get_children(cursor)
8
8
  children = []
9
- visit_children declaration, lambda { |child, child_parent, child_client_data|
9
+ visit_children cursor, lambda { |visit_result, child, child_parent, child_client_data|
10
10
  children << child
11
11
  :continue
12
12
  }, nil
13
13
  children
14
14
  end
15
-
15
+
16
16
  def get_spelling_location_data(location)
17
17
  file_ptr = FFI::MemoryPointer.new :pointer
18
18
  line_ptr = FFI::MemoryPointer.new :uint
@@ -28,61 +28,63 @@ class FFIGen
28
28
  Clang.tokenize translation_unit, range, tokens_ptr_ptr, num_tokens_ptr
29
29
  num_tokens = num_tokens_ptr.read_uint
30
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] }
31
+ (num_tokens - 1).times.map { |i| Clang::Token.new tokens_ptr[i] }
32
32
  end
33
33
  end
34
-
34
+
35
35
  class Clang::String
36
36
  def to_s
37
37
  Clang.get_c_string self
38
38
  end
39
-
39
+
40
40
  def to_s_and_dispose
41
41
  str = to_s
42
42
  Clang.dispose_string self
43
43
  str
44
44
  end
45
45
  end
46
-
46
+
47
47
  class Clang::Cursor
48
48
  def ==(other)
49
49
  other.is_a?(Clang::Cursor) && Clang.equal_cursors(self, other) == 1
50
50
  end
51
-
51
+
52
52
  def eql?(other)
53
53
  self == other
54
54
  end
55
-
55
+
56
56
  def hash
57
57
  Clang.hash_cursor self
58
58
  end
59
59
  end
60
-
60
+
61
61
  class Clang::Type
62
62
  def ==(other)
63
63
  other.is_a?(Clang::Type) && Clang.equal_types(self, other) == 1
64
64
  end
65
-
65
+
66
66
  def eql?(other)
67
67
  self == other
68
68
  end
69
-
69
+
70
70
  def hash
71
71
  0 # no hash available
72
72
  end
73
73
  end
74
-
75
- class Enum
74
+
75
+ class Type
76
+ end
77
+
78
+ class Enum < Type
76
79
  attr_accessor :name
77
- attr_reader :constants, :comment
78
-
79
- def initialize(generator, name, comment)
80
+
81
+ def initialize(generator, name, constants, description)
80
82
  @generator = generator
81
83
  @name = name
82
- @comment = comment
83
- @constants = []
84
+ @constants = constants
85
+ @description = description
84
86
  end
85
-
87
+
86
88
  def shorten_names
87
89
  return if @constants.size < 2
88
90
  names = @constants.map { |constant| constant[:name].parts }
@@ -90,47 +92,49 @@ class FFIGen
90
92
  names.each(&:pop) while names.map(&:last).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.last.downcase
91
93
  end
92
94
  end
93
-
94
- class StructOrUnion
95
- attr_accessor :name, :comment
95
+
96
+ class StructOrUnion < Type
97
+ attr_accessor :name, :description
96
98
  attr_reader :fields, :oo_functions, :written
97
-
99
+
98
100
  def initialize(generator, name, is_union)
99
101
  @generator = generator
100
102
  @name = name
101
103
  @is_union = is_union
102
- @comment = ""
104
+ @description = []
103
105
  @fields = []
104
106
  @oo_functions = []
105
107
  @written = false
106
108
  end
107
109
  end
108
-
109
- class FunctionOrCallback
110
- attr_reader :name, :parameters, :comment
111
- attr_accessor :return_type
112
-
113
- def initialize(generator, name, is_callback, blocking, comment)
110
+
111
+ class FunctionOrCallback < Type
112
+ attr_reader :name, :parameters, :return_type, :function_description, :return_value_description
113
+
114
+ def initialize(generator, name, parameters, return_type, is_callback, blocking, function_description, return_value_description)
114
115
  @generator = generator
115
116
  @name = name
116
- @parameters = []
117
+ @parameters = parameters
118
+ @return_type = return_type
117
119
  @is_callback = is_callback
118
120
  @blocking = blocking
119
- @comment = comment
121
+ @function_description = function_description
122
+ @return_value_description = return_value_description
120
123
  end
121
124
  end
122
-
123
- class Constant
124
- def initialize(generator, name, value)
125
+
126
+ class Define
127
+ def initialize(generator, name, parameters, value)
125
128
  @generator = generator
126
129
  @name = name
130
+ @parameters = parameters
127
131
  @value = value
128
132
  end
129
133
  end
130
-
134
+
131
135
  class Writer
132
136
  attr_reader :output
133
-
137
+
134
138
  def initialize(indentation_prefix, comment_prefix, comment_start = nil, comment_end = nil)
135
139
  @indentation_prefix = indentation_prefix
136
140
  @comment_prefix = comment_prefix
@@ -139,68 +143,53 @@ class FFIGen
139
143
  @current_indentation = ""
140
144
  @output = ""
141
145
  end
142
-
146
+
143
147
  def indent(prefix = @indentation_prefix)
144
148
  previous_indentation = @current_indentation
145
149
  @current_indentation += prefix
146
150
  yield
147
151
  @current_indentation = previous_indentation
148
152
  end
149
-
153
+
150
154
  def comment(&block)
151
155
  self.puts @comment_start unless @comment_start.nil?
152
156
  self.indent @comment_prefix, &block
153
157
  self.puts @comment_end unless @comment_end.nil?
154
158
  end
155
-
159
+
156
160
  def puts(*lines)
157
161
  lines.each do |line|
158
162
  @output << "#{@current_indentation}#{line}\n"
159
163
  end
160
164
  end
161
-
165
+
162
166
  def write_array(array, separator = "", first_line_prefix = "", other_lines_prefix = "")
163
167
  array.each_with_index do |entry, index|
164
168
  entry = yield entry if block_given?
165
169
  puts "#{index == 0 ? first_line_prefix : other_lines_prefix}#{entry}#{index < array.size - 1 ? separator : ''}"
166
170
  end
167
171
  end
168
-
169
- def prepare_comment_line(line)
170
- line = line.dup
171
- line.sub!(/\ ?\*+\/\s*$/, '')
172
- line.sub!(/^\s*\/?\*+ ?/, '')
173
- line.gsub!(/\\(brief|determine) /, '')
174
- line.gsub!('[', '(')
175
- line.gsub!(']', ')')
176
- line
177
- end
178
-
179
- def write_description(description, not_documented_message = true, first_line_prefix = "", other_lines_prefix = "")
180
- if description.is_a? String
181
- description = description.split("\n").map { |line| prepare_comment_line(line) }
182
- end
183
172
 
173
+ def write_description(description, not_documented_message = true, first_line_prefix = "", other_lines_prefix = "")
184
174
  description.shift while not description.empty? and description.first.strip.empty?
185
175
  description.pop while not description.empty? and description.last.strip.empty?
186
176
  description.map! { |line| line.gsub "\t", " " }
187
177
  space_prefix_length = description.map{ |line| line.index(/\S/) }.compact.min
188
178
  description.map! { |line| line[space_prefix_length..-1] }
189
179
  description << (not_documented_message ? "(Not documented)" : "") if description.empty?
190
-
180
+
191
181
  write_array description, "", first_line_prefix, other_lines_prefix
192
182
  end
193
183
  end
194
-
184
+
195
185
  class Name
196
- attr_reader :raw, :parts
197
-
198
- def initialize(generator, raw)
199
- @generator = generator
186
+ attr_reader :parts, :raw
187
+
188
+ def initialize(parts, raw = nil)
189
+ @parts = parts
200
190
  @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
191
  end
203
-
192
+
204
193
  def format(*modes, keyword_blacklist)
205
194
  parts = @parts.dup
206
195
  parts.map!(&:downcase) if modes.include? :downcase
@@ -212,28 +201,81 @@ class FFIGen
212
201
  str = "#{str}_" if keyword_blacklist.include? str
213
202
  str
214
203
  end
215
-
216
- def empty?
217
- @parts.empty?
204
+ end
205
+
206
+ class PrimitiveType < Type
207
+ def initialize(clang_type)
208
+ @clang_type = clang_type
209
+ end
210
+
211
+ def name
212
+ Name.new [@clang_type.to_s]
213
+ end
214
+ end
215
+
216
+ class StringType < Type
217
+ def name
218
+ Name.new ["string"]
219
+ end
220
+ end
221
+
222
+ class ByValueType < Type
223
+ def initialize(inner_type)
224
+ @inner_type = inner_type
225
+ end
226
+
227
+ def name
228
+ @inner_type.name
229
+ end
230
+ end
231
+
232
+ class PointerType < Type
233
+ attr_reader :pointee_name, :depth
234
+
235
+ def initialize(pointee_name, depth)
236
+ @pointee_name = pointee_name
237
+ @depth = depth
238
+ end
239
+
240
+ def name
241
+ @pointee_name
218
242
  end
219
243
  end
220
-
244
+
245
+ class ArrayType < Type
246
+ def initialize(element_type, constant_size)
247
+ @element_type = element_type
248
+ @constant_size = constant_size
249
+ end
250
+
251
+ def name
252
+ Name.new ["array"]
253
+ end
254
+ end
255
+
256
+ class UnknownType < Type
257
+ def name
258
+ Name.new ["unknown"]
259
+ end
260
+ end
261
+
221
262
  attr_reader :module_name, :ffi_lib, :headers, :prefixes, :output, :cflags
222
263
 
223
264
  def initialize(options = {})
224
265
  @module_name = options[:module_name] or fail "No module name given."
225
- @ffi_lib = options[:ffi_lib] or fail "No FFI library given."
266
+ @ffi_lib = options.fetch :ffi_lib, nil
226
267
  @headers = options[:headers] or fail "No headers given."
227
268
  @cflags = options.fetch :cflags, []
228
269
  @prefixes = options.fetch :prefixes, []
270
+ @suffixes = options.fetch :suffixes, []
229
271
  @blocking = options.fetch :blocking, []
230
272
  @ffi_lib_flags = options.fetch :ffi_lib_flags, nil
231
273
  @output = options.fetch :output, $stdout
232
-
274
+
233
275
  @translation_unit = nil
234
276
  @declarations = nil
235
277
  end
236
-
278
+
237
279
  def generate
238
280
  code = send "generate_#{File.extname(@output)[1..-1]}"
239
281
  if @output.is_a? String
@@ -243,10 +285,10 @@ class FFIGen
243
285
  @output.write code
244
286
  end
245
287
  end
246
-
288
+
247
289
  def translation_unit
248
290
  return @translation_unit unless @translation_unit.nil?
249
-
291
+
250
292
  args = []
251
293
  @headers.each do |header|
252
294
  args.push "-include", header unless header.is_a? Regexp
@@ -255,233 +297,463 @@ class FFIGen
255
297
  args_ptr = FFI::MemoryPointer.new :pointer, args.size
256
298
  pointers = args.map { |arg| FFI::MemoryPointer.from_string arg }
257
299
  args_ptr.write_array_of_pointer pointers
258
-
300
+
259
301
  index = Clang.create_index 0, 0
260
302
  @translation_unit = Clang.parse_translation_unit index, File.join(File.dirname(__FILE__), "ffi_gen/empty.h"), args_ptr, args.size, nil, 0, Clang.enum_type(:translation_unit_flags)[:detailed_preprocessing_record]
261
-
303
+
262
304
  Clang.get_num_diagnostics(@translation_unit).times do |i|
263
305
  diag = Clang.get_diagnostic @translation_unit, i
264
306
  $stderr.puts Clang.format_diagnostic(diag, Clang.default_diagnostic_display_options).to_s_and_dispose
265
307
  end
266
-
308
+
267
309
  @translation_unit
268
310
  end
269
-
311
+
270
312
  def declarations
271
313
  return @declarations unless @declarations.nil?
272
-
314
+
273
315
  header_files = []
274
316
  Clang.get_inclusions translation_unit, proc { |included_file, inclusion_stack, include_length, client_data|
275
317
  filename = Clang.get_file_name(included_file).to_s_and_dispose
276
318
  header_files << included_file if @headers.any? { |header| header.is_a?(Regexp) ? header =~ filename : filename.end_with?(header) }
277
319
  }, nil
278
-
279
- @declarations = {}
320
+
280
321
  unit_cursor = Clang.get_translation_unit_cursor translation_unit
322
+ declaration_cursors = Clang.get_children unit_cursor
323
+ declaration_cursors.delete_if { |cursor| [:macro_expansion, :inclusion_directive, :var_decl].include? cursor[:kind] }
324
+ declaration_cursors.delete_if { |cursor| !header_files.include?(Clang.get_spelling_location_data(Clang.get_cursor_location(cursor))[:file]) }
325
+
326
+ is_nested_declaration = []
327
+ min_offset = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration_cursors.last))[:offset]
328
+ declaration_cursors.reverse_each do |declaration_cursor|
329
+ offset = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration_cursor))[:offset]
330
+ is_nested_declaration.unshift(offset > min_offset)
331
+ min_offset = offset if offset < min_offset
332
+ end
333
+
334
+ @declarations = []
335
+ @declarations_by_name = {}
336
+ @declarations_by_type = {}
281
337
  previous_declaration_end = Clang.get_cursor_location unit_cursor
282
- Clang.get_children(unit_cursor).each do |declaration|
283
- file = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration))[:file]
284
-
285
- extent = Clang.get_cursor_extent declaration
286
- comment_range = Clang.get_range previous_declaration_end, Clang.get_range_start(extent)
287
- unless [:enum_decl, :struct_decl, :union_decl].include? declaration[:kind] # keep comment for typedef_decl
288
- previous_declaration_end = Clang.get_range_end extent
289
- end
290
-
291
- next if not header_files.include? file
292
-
293
- comment = extract_comment translation_unit, comment_range
294
-
295
- read_named_declaration declaration, comment
338
+ declaration_cursors.each_with_index do |declaration_cursor, index|
339
+ comment = []
340
+ unless is_nested_declaration[index]
341
+ comment_range = Clang.get_range previous_declaration_end, Clang.get_cursor_location(declaration_cursor)
342
+ comment, _ = extract_comment translation_unit, comment_range
343
+ previous_declaration_end = Clang.get_range_end Clang.get_cursor_extent(declaration_cursor)
344
+ end
345
+
346
+ read_declaration declaration_cursor, comment
296
347
  end
297
348
 
298
349
  @declarations
299
350
  end
300
-
301
- def read_named_declaration(declaration, comment)
302
- name = Name.new self, Clang.get_cursor_spelling(declaration).to_s_and_dispose
303
351
 
304
- case declaration[:kind]
352
+ def read_declaration(declaration_cursor, comment)
353
+ name = read_name declaration_cursor
354
+
355
+ declaration = case declaration_cursor[:kind]
305
356
  when :enum_decl
306
- enum = Enum.new self, name, comment
307
- @declarations[Clang.get_cursor_type(declaration)] = enum
308
-
309
- previous_constant_location = Clang.get_cursor_location declaration
357
+ enum_description = []
358
+ constant_descriptions = {}
359
+ current_description = enum_description
360
+ comment.each do |line|
361
+ if line.gsub!(/@(.*?): /, '')
362
+ current_description = []
363
+ constant_descriptions[$1] = current_description
364
+ end
365
+ current_description = enum_description if line.strip.empty?
366
+ current_description << line
367
+ end
368
+
369
+ constants = []
370
+ previous_constant_location = Clang.get_cursor_location declaration_cursor
310
371
  next_constant_value = 0
311
- Clang.get_children(declaration).each do |enum_constant|
312
- constant_name = Name.new self, Clang.get_cursor_spelling(enum_constant).to_s_and_dispose
313
-
372
+ Clang.get_children(declaration_cursor).each do |enum_constant|
373
+ constant_name = read_name enum_constant
374
+
314
375
  constant_location = Clang.get_cursor_location enum_constant
315
376
  constant_comment_range = Clang.get_range previous_constant_location, constant_location
316
- constant_comment = extract_comment translation_unit, constant_comment_range
377
+ constant_description, _ = extract_comment translation_unit, constant_comment_range
378
+ constant_description.concat(constant_descriptions[constant_name.raw] || [])
317
379
  previous_constant_location = constant_location
318
-
319
- catch :unsupported_value do
380
+
381
+ begin
320
382
  value_cursor = Clang.get_children(enum_constant).first
321
383
  constant_value = if value_cursor
322
- read_value value_cursor
384
+ parts = []
385
+ Clang.get_tokens(translation_unit, Clang.get_cursor_extent(value_cursor)).each do |token|
386
+ spelling = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
387
+ case Clang.get_token_kind(token)
388
+ when :literal
389
+ parts << spelling
390
+ when :punctuation
391
+ case spelling
392
+ when "+", "-", "<<", ">>", "(", ")"
393
+ parts << spelling
394
+ else
395
+ raise ArgumentError
396
+ end
397
+ else
398
+ raise ArgumentError
399
+ end
400
+ end
401
+ eval parts.join
323
402
  else
324
403
  next_constant_value
325
404
  end
326
-
327
- enum.constants << { name: constant_name, value: constant_value, comment: constant_comment }
405
+
406
+ constants << { name: constant_name, value: constant_value, comment: constant_description }
328
407
  next_constant_value = constant_value + 1
408
+ rescue ArgumentError
409
+ puts "Warning: Could not process value of enum constant \"#{constant_name.raw}\""
329
410
  end
330
411
  end
331
-
412
+
413
+ Enum.new self, name, constants, enum_description
414
+
332
415
  when :struct_decl, :union_decl
333
- struct = @declarations.delete(Clang.get_cursor_type(declaration)) || StructOrUnion.new(self, name, (declaration[:kind] == :union_decl))
416
+ struct = @declarations_by_type[Clang.get_cursor_type(declaration_cursor)] || StructOrUnion.new(self, name, (declaration_cursor[:kind] == :union_decl))
334
417
  raise if not struct.fields.empty?
335
- struct.comment << "\n#{comment}"
336
-
337
- struct_children = Clang.get_children declaration
338
- previous_field_end = Clang.get_cursor_location declaration
418
+ struct.description.concat comment
419
+
420
+ struct_children = Clang.get_children declaration_cursor
421
+ previous_field_end = Clang.get_cursor_location declaration_cursor
422
+ last_nested_declaration = nil
339
423
  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
343
-
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
349
-
350
- # check for comment starting on same line
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
353
- following_comment_token = extract_comment translation_unit, following_comment_range, false, false
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)
424
+ child = struct_children.shift
425
+ case child[:kind]
426
+ when :struct_decl, :union_decl
427
+ last_nested_declaration = read_declaration child, []
428
+ when :field_decl
429
+ field_name = read_name child
430
+ field_extent = Clang.get_cursor_extent child
431
+
432
+ field_comment_range = Clang.get_range previous_field_end, Clang.get_range_start(field_extent)
433
+ field_comment, _ = extract_comment translation_unit, field_comment_range
434
+
435
+ # check for comment starting on same line
436
+ next_field_start = struct_children.first ? Clang.get_cursor_location(struct_children.first) : Clang.get_range_end(Clang.get_cursor_extent(declaration_cursor))
437
+ following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start
438
+ following_comment, following_comment_token = extract_comment translation_unit, following_comment_range, false
439
+ 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]
440
+ field_comment = following_comment
441
+ previous_field_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
442
+ else
443
+ previous_field_end = Clang.get_range_end field_extent
444
+ end
445
+
446
+ field_type = resolve_type Clang.get_cursor_type(child)
447
+ last_nested_declaration.name ||= Name.new(name.parts + field_name.parts) if last_nested_declaration
448
+ last_nested_declaration = nil
449
+ struct.fields << { name: field_name, type: field_type, comment: field_comment }
357
450
  else
358
- previous_field_end = Clang.get_range_end field_extent
359
- end
360
-
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?
451
+ raise
365
452
  end
366
-
367
- field_type = Clang.get_cursor_type field
368
- struct.fields << { name: field_name, type: field_type, comment: field_comment }
369
453
  end
370
-
371
- @declarations[Clang.get_cursor_type(declaration)] = struct
372
-
454
+
455
+ struct
456
+
373
457
  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|
458
+ function_description = []
459
+ return_value_description = []
460
+ parameter_descriptions = {}
461
+ current_description = function_description
462
+ comment.each do |line|
463
+ if line.gsub!(/\\param (.*?) /, '')
464
+ current_description = []
465
+ parameter_descriptions[$1] = current_description
466
+ end
467
+ current_description = return_value_description if line.gsub! '\\returns ', ''
468
+ current_description << line
469
+ end
470
+
471
+ return_type = resolve_type Clang.get_cursor_result_type(declaration_cursor)
472
+ parameters = []
473
+ first_parameter_type = nil
474
+ Clang.get_children(declaration_cursor).each do |function_child|
379
475
  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
476
+ param_name = read_name function_child
382
477
  tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
383
478
  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 }
479
+ param_type = resolve_type Clang.get_cursor_type(function_child), is_array
480
+ param_name ||= param_type.name
481
+ param_name ||= Name.new []
482
+ first_parameter_type ||= Clang.get_cursor_type function_child
483
+ parameters << { name: param_name, type: param_type }
484
+ end
485
+
486
+ parameters.each_with_index do |parameter, index|
487
+ parameter[:description] = parameter[:name] && parameter_descriptions[parameter[:name].raw]
488
+ parameter[:description] ||= parameter_descriptions.values[index] if parameter_descriptions.size == parameters.size # workaround for wrong names
489
+ parameter[:description] ||= []
385
490
  end
386
-
387
- pointee_declaration = function.parameters.first && get_pointee_declaration(function.parameters.first[:type])
491
+
492
+ function = FunctionOrCallback.new self, name, parameters, return_type, false, @blocking.include?(name.raw), function_description, return_value_description
493
+
494
+ pointee_declaration = first_parameter_type && get_pointee_declaration(first_parameter_type)
388
495
  if pointee_declaration
389
496
  type_prefix = pointee_declaration.name.parts.join.downcase
390
497
  function_name_parts = name.parts.dup
391
498
  while type_prefix.start_with? function_name_parts.first.downcase
392
499
  type_prefix = type_prefix[function_name_parts.first.size..-1]
393
500
  function_name_parts.shift
501
+ break if function_name_parts.empty?
394
502
  end
395
503
  if type_prefix.empty?
396
- pointee_declaration.oo_functions << [Name.new(self, function_name_parts), function, get_pointee_declaration(function.return_type)]
504
+ pointee_declaration.oo_functions << [Name.new(function_name_parts), function]
397
505
  end
398
506
  end
399
-
507
+
508
+ function
509
+
400
510
  when :typedef_decl
401
- typedef_children = Clang.get_children declaration
511
+ typedef_children = Clang.get_children declaration_cursor
402
512
  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
-
513
+ child_declaration = @declarations_by_type[Clang.get_cursor_type(typedef_children.first)]
514
+ child_declaration.name = name if child_declaration and child_declaration.name.nil?
515
+ nil
406
516
  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 }
517
+ return_type = resolve_type Clang.get_cursor_type(typedef_children.first)
518
+ parameters = []
519
+ typedef_children.each do |param_decl|
520
+ param_name = read_name param_decl
521
+ param_type = resolve_type Clang.get_cursor_type(param_decl)
522
+ param_name ||= param_type.name
523
+ parameters << { name:param_name, type: param_type, description: [] }
415
524
  end
525
+ FunctionOrCallback.new self, name, parameters, return_type, true, false, comment, []
526
+ else
527
+ nil
416
528
  end
417
-
529
+
418
530
  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
531
+ tokens = Clang.get_tokens(translation_unit, Clang.get_cursor_extent(declaration_cursor)).map { |token|
532
+ [Clang.get_token_kind(token), Clang.get_token_spelling(translation_unit, token).to_s_and_dispose]
533
+ }
534
+ if tokens.size > 1
535
+ tokens.shift
536
+ begin
537
+ parameters = nil
538
+ if tokens.first[1] == "("
539
+ tokens_backup = tokens.dup
540
+ begin
541
+ parameters = []
542
+ tokens.shift
543
+ loop do
544
+ kind, spelling = tokens.shift
545
+ case kind
546
+ when :identifier
547
+ parameters << spelling
548
+ when :punctuation
549
+ break if spelling == ")"
550
+ raise ArgumentError unless spelling == ","
551
+ else
552
+ raise ArgumentError
553
+ end
554
+ end
555
+ rescue ArgumentError
556
+ parameters = nil
557
+ tokens = tokens_backup
558
+ end
559
+ end
560
+ value = []
561
+ until tokens.empty?
562
+ kind, spelling = tokens.shift
563
+ case kind
564
+ when :literal
565
+ value << spelling
566
+ when :punctuation
567
+ case spelling
568
+ when "+", "-", "<<", ">>", ")"
569
+ value << spelling
570
+ when ","
571
+ value << ", "
572
+ when "("
573
+ if tokens[1][1] == ")"
574
+ tokens.delete_at 1
575
+ else
576
+ value << spelling
577
+ end
578
+ else
579
+ raise ArgumentError
580
+ end
581
+ when :identifier
582
+ raise ArgumentError unless parameters
583
+ if parameters.include? spelling
584
+ value << spelling
585
+ elsif spelling == "NULL"
586
+ value << "nil"
587
+ else
588
+ if not tokens.empty? and tokens.first[1] == "("
589
+ tokens.shift
590
+ if spelling == "strlen"
591
+ argument_kind, argument_spelling = tokens.shift
592
+ second_token_kind, second_token_spelling = tokens.shift
593
+ raise ArgumentError unless argument_kind == :identifier and second_token_spelling == ")"
594
+ value << "#{argument_spelling}.length"
595
+ else
596
+ value << [:method, read_name(spelling)]
597
+ value << "("
598
+ end
599
+ else
600
+ value << [:constant, read_name(spelling)]
601
+ end
602
+ end
603
+ when :keyword
604
+ raise ArgumentError unless spelling == "sizeof" and tokens[0][1] == "(" and tokens[1][0] == :literal and tokens[2][1] == ")"
605
+ tokens.shift
606
+ argument_kind, argument_spelling = tokens.shift
607
+ value << "#{argument_spelling}.length"
608
+ tokens.shift
609
+ else
610
+ raise ArgumentError
611
+ end
612
+ end
613
+ Define.new(self, name, parameters, value)
614
+ rescue ArgumentError
615
+ puts "Warning: Could not process value of macro \"#{name.raw}\""
616
+ nil
617
+ end
618
+ else
619
+ nil
426
620
  end
427
-
621
+
622
+ else
623
+ raise declaration_cursor[:kind].to_s
624
+
428
625
  end
626
+
627
+ return nil if declaration.nil?
628
+ @declarations.delete declaration
629
+ @declarations << declaration
630
+ @declarations_by_name[name] = name.raw unless name.nil?
631
+ type = Clang.get_cursor_type declaration_cursor
632
+ @declarations_by_type[type] = declaration unless type.nil?
633
+
634
+ declaration
429
635
  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
636
+
637
+ def resolve_type(full_type, is_array = false)
638
+ canonical_type = Clang.get_canonical_type full_type
639
+ data_array = case canonical_type[:kind]
640
+ when :void, :bool, :u_char, :u_short, :u_int, :u_long, :u_long_long, :char_s, :s_char, :short, :int, :long, :long_long, :float, :double
641
+ PrimitiveType.new canonical_type[:kind]
642
+ when :pointer
643
+ if is_array
644
+ ArrayType.new resolve_type(Clang.get_pointee_type(canonical_type)), nil
645
+ else
646
+ pointee_type = Clang.get_pointee_type canonical_type
647
+ type = case pointee_type[:kind]
648
+ when :char_s
649
+ StringType.new
650
+ when :record
651
+ @declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
652
+ when :function_proto
653
+ @declarations_by_type[full_type]
445
654
  else
446
- throw :unsupported_value
655
+ nil
447
656
  end
448
- else
449
- throw :unsupported_value
657
+
658
+ if type.nil?
659
+ pointer_depth = 0
660
+ pointee_name = ""
661
+ current_type = full_type
662
+ loop do
663
+ declaration_cursor = Clang.get_type_declaration current_type
664
+ pointee_name = read_name declaration_cursor
665
+ break if pointee_name
666
+
667
+ case current_type[:kind]
668
+ when :pointer
669
+ pointer_depth += 1
670
+ current_type = Clang.get_pointee_type current_type
671
+ when :unexposed
672
+ break
673
+ else
674
+ pointee_name = Name.new Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose.split("_")
675
+ break
676
+ end
677
+ end
678
+ type = PointerType.new pointee_name, pointer_depth
679
+ end
680
+
681
+ type
450
682
  end
683
+ when :record
684
+ type = @declarations_by_type[canonical_type]
685
+ type &&= ByValueType.new(type)
686
+ type || UnknownType.new # TODO
687
+ when :enum
688
+ @declarations_by_type[canonical_type] || UnknownType.new # TODO
689
+ when :constant_array
690
+ ArrayType.new resolve_type(Clang.get_array_element_type(canonical_type)), Clang.get_array_size(canonical_type)
691
+ when :unexposed, :function_proto
692
+ UnknownType.new
693
+ when :incomplete_array
694
+ PointerType.new resolve_type(Clang.get_array_element_type(canonical_type)).name, 1
695
+ else
696
+ raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}"
451
697
  end
452
- eval parts.join
453
698
  end
454
-
699
+
700
+ def read_name(source)
701
+ source = Clang.get_cursor_spelling(source).to_s_and_dispose if source.is_a? Clang::Cursor
702
+ return nil if source.empty?
703
+ trimmed = source.sub(/^(#{@prefixes.join('|')})/, '')
704
+ trimmed = trimmed.sub(/(#{@suffixes.join('|')})$/, '')
705
+ parts = trimmed.split(/_|(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).reject(&:empty?)
706
+ Name.new parts, source
707
+ end
708
+
455
709
  def get_pointee_declaration(type)
456
710
  canonical_type = Clang.get_canonical_type type
457
711
  return nil if canonical_type[:kind] != :pointer
458
712
  pointee_type = Clang.get_pointee_type canonical_type
459
713
  return nil if pointee_type[:kind] != :record
460
- @declarations[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
714
+ @declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
461
715
  end
462
-
463
- def extract_comment(translation_unit, range, search_backwards = true, return_spelling = true)
716
+
717
+ def extract_comment(translation_unit, range, search_backwards = true)
464
718
  tokens = Clang.get_tokens translation_unit, range
719
+
465
720
  iterator = search_backwards ? tokens.reverse_each : tokens.each
721
+ comment_lines = []
722
+ comment_token = nil
723
+ comment_block = false
466
724
  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
725
+ next if Clang.get_token_kind(token) != :comment
726
+ comment = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
727
+ lines = comment.split("\n").map { |line|
728
+ line.sub!(/\ ?\*+\/\s*$/, '')
729
+ line.sub!(/^\s*\/?[*\/]+ ?/, '')
730
+ line.gsub!(/\\(brief|determine) /, '')
731
+ line.gsub!('[', '(')
732
+ line.gsub!(']', ')')
733
+ line
734
+ }
735
+ comment_lines = lines + comment_lines
736
+ comment_token = token
737
+ comment_block = !comment_block if comment == "///"
738
+ break unless comment_block and search_backwards
470
739
  end
471
- ""
740
+
741
+ return comment_lines, comment_token
472
742
  end
473
-
743
+
474
744
  def self.generate(options = {})
475
745
  self.new(options).generate
476
746
  end
477
-
747
+
748
+ require "ffi_gen/ruby_output"
749
+ require "ffi_gen/java_output"
478
750
  end
479
751
 
480
752
  if __FILE__ == $0
481
753
  FFIGen.generate(
482
754
  module_name: "FFIGen::Clang",
483
- ffi_lib: "clang",
484
- headers: ["clang-c/Index.h"],
755
+ ffi_lib: ["libclang-3.5.so.1", "libclang.so.1", "clang"],
756
+ headers: ["clang-c/CXErrorCode.h", "clang-c/CXString.h", "clang-c/Index.h"],
485
757
  cflags: `llvm-config --cflags`.split(" "),
486
758
  prefixes: ["clang_", "CX"],
487
759
  output: File.join(File.dirname(__FILE__), "ffi_gen/clang.rb")