ffi_gen 1.1.0 → 1.2.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,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")