ghazel-ffi_gen 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ffi_gen.rb ADDED
@@ -0,0 +1,763 @@
1
+ require 'ffi'
2
+
3
+ class FFIGen
4
+ require "ffi_gen/clang"
5
+
6
+ class << Clang
7
+ def get_children(cursor)
8
+ children = []
9
+ visit_children cursor, lambda { |visit_result, child, child_parent, child_client_data|
10
+ children << child
11
+ :continue
12
+ }, nil
13
+ children
14
+ end
15
+
16
+ def get_spelling_location_data(location)
17
+ file_ptr = FFI::MemoryPointer.new :pointer
18
+ line_ptr = FFI::MemoryPointer.new :uint
19
+ column_ptr = FFI::MemoryPointer.new :uint
20
+ offset_ptr = FFI::MemoryPointer.new :uint
21
+ get_spelling_location location, file_ptr, line_ptr, column_ptr, offset_ptr
22
+ { file: file_ptr.read_pointer, line: line_ptr.read_uint, column: column_ptr.read_uint, offset: offset_ptr.read_uint }
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 - 1).times.map { |i| Clang::Token.new tokens_ptr[i] }
32
+ end
33
+ end
34
+
35
+ class Clang::String
36
+ def to_s
37
+ Clang.get_c_string self
38
+ end
39
+
40
+ def to_s_and_dispose
41
+ str = to_s
42
+ Clang.dispose_string self
43
+ str
44
+ end
45
+ end
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
+
75
+ class Type
76
+ end
77
+
78
+ class Enum < Type
79
+ attr_accessor :name
80
+
81
+ def initialize(generator, name, constants, description)
82
+ @generator = generator
83
+ @name = name
84
+ @constants = constants
85
+ @description = description
86
+ end
87
+
88
+ def shorten_names
89
+ return if @constants.size < 2
90
+ names = @constants.map { |constant| constant[:name].parts }
91
+ names.each(&:shift) while names.map(&:first).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.first.downcase
92
+ names.each(&:pop) while names.map(&:last).uniq.size == 1 and @name.parts.map(&:downcase).include? names.first.last.downcase
93
+ end
94
+ end
95
+
96
+ class StructOrUnion < Type
97
+ attr_accessor :name, :description
98
+ attr_reader :fields, :oo_functions, :written
99
+
100
+ def initialize(generator, name, is_union)
101
+ @generator = generator
102
+ @name = name
103
+ @is_union = is_union
104
+ @description = []
105
+ @fields = []
106
+ @oo_functions = []
107
+ @written = false
108
+ end
109
+ end
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)
115
+ @generator = generator
116
+ @name = name
117
+ @parameters = parameters
118
+ @return_type = return_type
119
+ @is_callback = is_callback
120
+ @blocking = blocking
121
+ @function_description = function_description
122
+ @return_value_description = return_value_description
123
+ end
124
+ end
125
+
126
+ class Define
127
+ def initialize(generator, name, parameters, value)
128
+ @generator = generator
129
+ @name = name
130
+ @parameters = parameters
131
+ @value = value
132
+ end
133
+ end
134
+
135
+ class Writer
136
+ attr_reader :output
137
+
138
+ def initialize(indentation_prefix, comment_prefix, comment_start = nil, comment_end = nil)
139
+ @indentation_prefix = indentation_prefix
140
+ @comment_prefix = comment_prefix
141
+ @comment_start = comment_start
142
+ @comment_end = comment_end
143
+ @current_indentation = ""
144
+ @output = ""
145
+ end
146
+
147
+ def indent(prefix = @indentation_prefix)
148
+ previous_indentation = @current_indentation
149
+ @current_indentation += prefix
150
+ yield
151
+ @current_indentation = previous_indentation
152
+ end
153
+
154
+ def comment(&block)
155
+ self.puts @comment_start unless @comment_start.nil?
156
+ self.indent @comment_prefix, &block
157
+ self.puts @comment_end unless @comment_end.nil?
158
+ end
159
+
160
+ def puts(*lines)
161
+ lines.each do |line|
162
+ @output << "#{@current_indentation}#{line}\n"
163
+ end
164
+ end
165
+
166
+ def write_array(array, separator = "", first_line_prefix = "", other_lines_prefix = "")
167
+ array.each_with_index do |entry, index|
168
+ entry = yield entry if block_given?
169
+ puts "#{index == 0 ? first_line_prefix : other_lines_prefix}#{entry}#{index < array.size - 1 ? separator : ''}"
170
+ end
171
+ end
172
+
173
+ def write_description(description, not_documented_message = true, first_line_prefix = "", other_lines_prefix = "")
174
+ description.shift while not description.empty? and description.first.strip.empty?
175
+ description.pop while not description.empty? and description.last.strip.empty?
176
+ description.map! { |line| line.gsub "\t", " " }
177
+ space_prefix_length = description.map{ |line| line.index(/\S/) }.compact.min
178
+ description.map! { |line| line[space_prefix_length..-1] }
179
+ description << (not_documented_message ? "(Not documented)" : "") if description.empty?
180
+
181
+ write_array description, "", first_line_prefix, other_lines_prefix
182
+ end
183
+ end
184
+
185
+ class Name
186
+ attr_reader :parts, :raw
187
+
188
+ def initialize(parts, raw = nil)
189
+ @parts = parts
190
+ @raw = raw
191
+ end
192
+
193
+ def format(*modes, keyword_blacklist)
194
+ parts = @parts.dup
195
+ parts.map!(&:downcase) if modes.include? :downcase
196
+ parts.map!(&:upcase) if modes.include? :upcase
197
+ parts.map! { |s| s[0].upcase + s[1..-1] } if modes.include? :camelcase
198
+ parts[0] = parts[0][0].downcase + parts[0][1..-1] if modes.include? :initial_downcase
199
+ str = parts.join(modes.include?(:underscores) ? "_" : "")
200
+ str.sub!(/^\d/, '_\0') # fix illegal beginnings
201
+ str = "#{str}_" if keyword_blacklist.include? str
202
+ str
203
+ end
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
242
+ end
243
+ end
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
+
262
+ attr_reader :module_name, :ffi_lib, :headers, :prefixes, :output, :cflags
263
+
264
+ def initialize(options = {})
265
+ @module_name = options[:module_name] or fail "No module name given."
266
+ @ffi_lib = options.fetch :ffi_lib, nil
267
+ @headers = options[:headers] or fail "No headers given."
268
+ @cflags = options.fetch :cflags, []
269
+ @prefixes = options.fetch :prefixes, []
270
+ @suffixes = options.fetch :suffixes, []
271
+ @blocking = options.fetch :blocking, []
272
+ @ffi_lib_flags = options.fetch :ffi_lib_flags, nil
273
+ @output = options.fetch :output, $stdout
274
+
275
+ @translation_unit = nil
276
+ @declarations = nil
277
+ end
278
+
279
+ def generate
280
+ code = send "generate_#{File.extname(@output)[1..-1]}"
281
+ if @output.is_a? String
282
+ File.open(@output, "w") { |file| file.write code }
283
+ puts "ffi_gen: #{@output}"
284
+ else
285
+ @output.write code
286
+ end
287
+ end
288
+
289
+ def translation_unit
290
+ return @translation_unit unless @translation_unit.nil?
291
+
292
+ args = []
293
+ @headers.each do |header|
294
+ args.push "-include", header unless header.is_a? Regexp
295
+ end
296
+ args.concat @cflags
297
+ args_ptr = FFI::MemoryPointer.new :pointer, args.size
298
+ pointers = args.map { |arg| FFI::MemoryPointer.from_string arg }
299
+ args_ptr.write_array_of_pointer pointers
300
+
301
+ index = Clang.create_index 0, 0
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]
303
+
304
+ Clang.get_num_diagnostics(@translation_unit).times do |i|
305
+ diag = Clang.get_diagnostic @translation_unit, i
306
+ $stderr.puts Clang.format_diagnostic(diag, Clang.default_diagnostic_display_options).to_s_and_dispose
307
+ end
308
+
309
+ @translation_unit
310
+ end
311
+
312
+ def declarations
313
+ return @declarations unless @declarations.nil?
314
+
315
+ header_files = []
316
+ Clang.get_inclusions translation_unit, proc { |included_file, inclusion_stack, include_length, client_data|
317
+ filename = Clang.get_file_name(included_file).to_s_and_dispose
318
+ header_files << included_file if @headers.any? { |header| header.is_a?(Regexp) ? header =~ filename : filename.end_with?(header) }
319
+ }, nil
320
+
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 = {}
337
+ previous_declaration_end = Clang.get_cursor_location unit_cursor
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
347
+ end
348
+
349
+ @declarations
350
+ end
351
+
352
+ def read_declaration(declaration_cursor, comment)
353
+ name = read_name declaration_cursor
354
+
355
+ declaration = case declaration_cursor[:kind]
356
+ when :enum_decl
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
371
+ next_constant_value = 0
372
+ Clang.get_children(declaration_cursor).each do |enum_constant|
373
+ constant_name = read_name enum_constant
374
+ next if constant_name.nil?
375
+
376
+ constant_location = Clang.get_cursor_location enum_constant
377
+ constant_comment_range = Clang.get_range previous_constant_location, constant_location
378
+ constant_description, _ = extract_comment translation_unit, constant_comment_range
379
+ constant_description.concat(constant_descriptions[constant_name.raw] || [])
380
+ previous_constant_location = constant_location
381
+
382
+ begin
383
+ value_cursor = Clang.get_children(enum_constant).first
384
+ constant_value = if value_cursor
385
+ parts = []
386
+ Clang.get_tokens(translation_unit, Clang.get_cursor_extent(value_cursor)).each do |token|
387
+ spelling = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
388
+ case Clang.get_token_kind(token)
389
+ when :literal
390
+ parts << spelling
391
+ when :punctuation
392
+ case spelling
393
+ when "+", "-", "<<", ">>", "(", ")"
394
+ parts << spelling
395
+ else
396
+ raise ArgumentError
397
+ end
398
+ else
399
+ raise ArgumentError
400
+ end
401
+ end
402
+ eval parts.join
403
+ else
404
+ next_constant_value
405
+ end
406
+
407
+ constants << { name: constant_name, value: constant_value, comment: constant_description }
408
+ next_constant_value = constant_value + 1
409
+ rescue ArgumentError
410
+ puts "Warning: Could not process value of enum constant \"#{constant_name.raw}\""
411
+ end
412
+ end
413
+
414
+ Enum.new self, name, constants, enum_description
415
+
416
+ when :struct_decl, :union_decl
417
+ struct = @declarations_by_type[Clang.get_cursor_type(declaration_cursor)] || StructOrUnion.new(self, name, (declaration_cursor[:kind] == :union_decl))
418
+ raise if not struct.fields.empty?
419
+ struct.description.concat comment
420
+
421
+ struct_children = Clang.get_children declaration_cursor
422
+ previous_field_end = Clang.get_cursor_location declaration_cursor
423
+ last_nested_declaration = nil
424
+ until struct_children.empty?
425
+ child = struct_children.shift
426
+ case child[:kind]
427
+ when :struct_decl, :union_decl
428
+ last_nested_declaration = read_declaration child, []
429
+ when :field_decl
430
+ field_name = read_name child
431
+ field_extent = Clang.get_cursor_extent child
432
+
433
+ field_comment_range = Clang.get_range previous_field_end, Clang.get_range_start(field_extent)
434
+ field_comment, _ = extract_comment translation_unit, field_comment_range
435
+
436
+ # check for comment starting on same line
437
+ next_field_start = struct_children.first ? Clang.get_cursor_location(struct_children.first) : Clang.get_range_end(Clang.get_cursor_extent(declaration_cursor))
438
+ following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start
439
+ following_comment, following_comment_token = extract_comment translation_unit, following_comment_range, false
440
+ 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]
441
+ field_comment = following_comment
442
+ previous_field_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
443
+ else
444
+ previous_field_end = Clang.get_range_end field_extent
445
+ end
446
+
447
+ field_type = resolve_type Clang.get_cursor_type(child)
448
+ last_nested_declaration.name ||= Name.new(name.parts + field_name.parts) if last_nested_declaration
449
+ last_nested_declaration = nil
450
+ struct.fields << { name: field_name, type: field_type, comment: field_comment }
451
+ when :unexposed_attr
452
+ else
453
+ raise
454
+ end
455
+ end
456
+
457
+ struct
458
+
459
+ when :function_decl
460
+ function_description = []
461
+ return_value_description = []
462
+ parameter_descriptions = {}
463
+ current_description = function_description
464
+ comment.each do |line|
465
+ if line.gsub!(/\\param (.*?) /, '')
466
+ current_description = []
467
+ parameter_descriptions[$1] = current_description
468
+ end
469
+ current_description = return_value_description if line.gsub! '\\returns ', ''
470
+ current_description << line
471
+ end
472
+
473
+ return_type = resolve_type Clang.get_cursor_result_type(declaration_cursor)
474
+ parameters = []
475
+ first_parameter_type = nil
476
+ Clang.get_children(declaration_cursor).each do |function_child|
477
+ next if function_child[:kind] != :parm_decl
478
+ param_name = read_name function_child
479
+ tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
480
+ is_array = tokens.any? { |t| Clang.get_token_spelling(translation_unit, t).to_s_and_dispose == "[" }
481
+ param_type = resolve_type Clang.get_cursor_type(function_child), is_array
482
+ param_name ||= param_type.name
483
+ param_name ||= Name.new []
484
+ first_parameter_type ||= Clang.get_cursor_type function_child
485
+ parameters << { name: param_name, type: param_type }
486
+ end
487
+
488
+ parameters.each_with_index do |parameter, index|
489
+ parameter[:description] = parameter[:name] && parameter_descriptions[parameter[:name].raw]
490
+ parameter[:description] ||= parameter_descriptions.values[index] if parameter_descriptions.size == parameters.size # workaround for wrong names
491
+ parameter[:description] ||= []
492
+ end
493
+
494
+ function = FunctionOrCallback.new self, name, parameters, return_type, false, @blocking.include?(name.raw), function_description, return_value_description
495
+
496
+ pointee_declaration = first_parameter_type && get_pointee_declaration(first_parameter_type)
497
+ if pointee_declaration
498
+ type_prefix = pointee_declaration.name.parts.join.downcase
499
+ function_name_parts = name.parts.dup
500
+ while type_prefix.start_with? function_name_parts.first.downcase
501
+ type_prefix = type_prefix[function_name_parts.first.size..-1]
502
+ function_name_parts.shift
503
+ break if function_name_parts.empty?
504
+ end
505
+ if type_prefix.empty?
506
+ pointee_declaration.oo_functions << [Name.new(function_name_parts), function]
507
+ end
508
+ end
509
+
510
+ function
511
+
512
+ when :typedef_decl
513
+ typedef_children = Clang.get_children declaration_cursor
514
+ if typedef_children.size == 1
515
+ child_declaration = @declarations_by_type[Clang.get_cursor_type(typedef_children.first)]
516
+ child_declaration.name = name if child_declaration and child_declaration.name.nil?
517
+ nil
518
+ elsif typedef_children.size > 1
519
+ return_type = resolve_type Clang.get_cursor_type(typedef_children.first)
520
+ parameters = []
521
+ typedef_children.each do |param_decl|
522
+ param_name = read_name param_decl
523
+ param_type = resolve_type Clang.get_cursor_type(param_decl)
524
+ param_name ||= param_type.name
525
+ parameters << { name:param_name, type: param_type, description: [] }
526
+ end
527
+ FunctionOrCallback.new self, name, parameters, return_type, true, false, comment, []
528
+ else
529
+ nil
530
+ end
531
+
532
+ when :macro_definition
533
+ tokens = Clang.get_tokens(translation_unit, Clang.get_cursor_extent(declaration_cursor)).map { |token|
534
+ [Clang.get_token_kind(token), Clang.get_token_spelling(translation_unit, token).to_s_and_dispose]
535
+ }
536
+ if tokens.size > 1
537
+ tokens.shift
538
+ begin
539
+ parameters = nil
540
+ if tokens.first[1] == "("
541
+ tokens_backup = tokens.dup
542
+ begin
543
+ parameters = []
544
+ tokens.shift
545
+ loop do
546
+ kind, spelling = tokens.shift
547
+ case kind
548
+ when :identifier
549
+ parameters << spelling
550
+ when :punctuation
551
+ break if spelling == ")"
552
+ raise ArgumentError unless spelling == ","
553
+ else
554
+ raise ArgumentError
555
+ end
556
+ end
557
+ rescue ArgumentError
558
+ parameters = nil
559
+ tokens = tokens_backup
560
+ end
561
+ end
562
+ value = []
563
+ until tokens.empty?
564
+ kind, spelling = tokens.shift
565
+ case kind
566
+ when :literal
567
+ value << spelling
568
+ when :punctuation
569
+ case spelling
570
+ when "+", "-", "<<", ">>", ")"
571
+ value << spelling
572
+ when ","
573
+ value << ", "
574
+ when "("
575
+ if tokens[1][1] == ")"
576
+ tokens.delete_at 1
577
+ else
578
+ value << spelling
579
+ end
580
+ else
581
+ raise ArgumentError
582
+ end
583
+ when :identifier
584
+ raise ArgumentError unless parameters
585
+ if parameters.include? spelling
586
+ value << spelling
587
+ elsif spelling == "NULL"
588
+ value << "nil"
589
+ else
590
+ if not tokens.empty? and tokens.first[1] == "("
591
+ tokens.shift
592
+ if spelling == "strlen"
593
+ argument_kind, argument_spelling = tokens.shift
594
+ second_token_kind, second_token_spelling = tokens.shift
595
+ raise ArgumentError unless argument_kind == :identifier and second_token_spelling == ")"
596
+ value << "#{argument_spelling}.length"
597
+ else
598
+ value << [:method, read_name(spelling)]
599
+ value << "("
600
+ end
601
+ else
602
+ value << [:constant, read_name(spelling)]
603
+ end
604
+ end
605
+ when :keyword
606
+ raise ArgumentError unless spelling == "sizeof" and tokens[0][1] == "(" and tokens[1][0] == :literal and tokens[2][1] == ")"
607
+ tokens.shift
608
+ argument_kind, argument_spelling = tokens.shift
609
+ value << "#{argument_spelling}.length"
610
+ tokens.shift
611
+ else
612
+ raise ArgumentError
613
+ end
614
+ end
615
+ Define.new(self, name, parameters, value)
616
+ rescue ArgumentError
617
+ puts "Warning: Could not process value of macro \"#{name.raw}\""
618
+ nil
619
+ end
620
+ else
621
+ nil
622
+ end
623
+
624
+ else
625
+ raise declaration_cursor[:kind].to_s
626
+
627
+ end
628
+
629
+ return nil if declaration.nil?
630
+ @declarations.delete declaration
631
+ @declarations << declaration
632
+ @declarations_by_name[name] = name.raw unless name.nil?
633
+ type = Clang.get_cursor_type declaration_cursor
634
+ @declarations_by_type[type] = declaration unless type.nil?
635
+
636
+ declaration
637
+ end
638
+
639
+ def resolve_type(full_type, is_array = false)
640
+ canonical_type = Clang.get_canonical_type full_type
641
+ data_array = case canonical_type[:kind]
642
+ 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
643
+ PrimitiveType.new canonical_type[:kind]
644
+ when :pointer
645
+ if is_array
646
+ ArrayType.new resolve_type(Clang.get_pointee_type(canonical_type)), nil
647
+ else
648
+ pointee_type = Clang.get_pointee_type canonical_type
649
+ type = case pointee_type[:kind]
650
+ when :char_s
651
+ StringType.new
652
+ when :record
653
+ @declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
654
+ when :function_proto
655
+ @declarations_by_type[full_type]
656
+ else
657
+ nil
658
+ end
659
+
660
+ if type.nil?
661
+ pointer_depth = 0
662
+ pointee_name = ""
663
+ current_type = full_type
664
+ loop do
665
+ declaration_cursor = Clang.get_type_declaration current_type
666
+ pointee_name = read_name declaration_cursor
667
+ break if pointee_name
668
+
669
+ case current_type[:kind]
670
+ when :pointer
671
+ pointer_depth += 1
672
+ current_type = Clang.get_pointee_type current_type
673
+ when :unexposed
674
+ break
675
+ else
676
+ pointee_name = Name.new Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose.split("_")
677
+ break
678
+ end
679
+ end
680
+ type = PointerType.new pointee_name, pointer_depth
681
+ end
682
+
683
+ type
684
+ end
685
+ when :record
686
+ type = @declarations_by_type[canonical_type]
687
+ type &&= ByValueType.new(type)
688
+ type || UnknownType.new # TODO
689
+ when :enum
690
+ @declarations_by_type[canonical_type] || UnknownType.new # TODO
691
+ when :constant_array
692
+ ArrayType.new resolve_type(Clang.get_array_element_type(canonical_type)), Clang.get_array_size(canonical_type)
693
+ when :unexposed, :function_proto
694
+ UnknownType.new
695
+ when :incomplete_array
696
+ PointerType.new resolve_type(Clang.get_array_element_type(canonical_type)).name, 1
697
+ else
698
+ raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}"
699
+ end
700
+ end
701
+
702
+ def read_name(source)
703
+ source = Clang.get_cursor_spelling(source).to_s_and_dispose if source.is_a? Clang::Cursor
704
+ return nil if source.empty?
705
+ trimmed = source.sub(/^(#{@prefixes.join('|')})/, '')
706
+ trimmed = trimmed.sub(/(#{@suffixes.join('|')})$/, '')
707
+ parts = trimmed.split(/_|(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).reject(&:empty?)
708
+ Name.new parts, source
709
+ end
710
+
711
+ def get_pointee_declaration(type)
712
+ canonical_type = Clang.get_canonical_type type
713
+ return nil if canonical_type[:kind] != :pointer
714
+ pointee_type = Clang.get_pointee_type canonical_type
715
+ return nil if pointee_type[:kind] != :record
716
+ @declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
717
+ end
718
+
719
+ def extract_comment(translation_unit, range, search_backwards = true)
720
+ tokens = Clang.get_tokens translation_unit, range
721
+
722
+ iterator = search_backwards ? tokens.reverse_each : tokens.each
723
+ comment_lines = []
724
+ comment_token = nil
725
+ comment_block = false
726
+ iterator.each do |token|
727
+ next if Clang.get_token_kind(token) != :comment
728
+ comment = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
729
+ lines = comment.split("\n").map { |line|
730
+ line.sub!(/\ ?\*+\/\s*$/, '')
731
+ line.sub!(/^\s*\/?[*\/]+ ?/, '')
732
+ line.gsub!(/\\(brief|determine) /, '')
733
+ line.gsub!('[', '(')
734
+ line.gsub!(']', ')')
735
+ line
736
+ }
737
+ comment_lines = lines + comment_lines
738
+ comment_token = token
739
+ comment_block = !comment_block if comment == "///"
740
+ break unless comment_block and search_backwards
741
+ end
742
+
743
+ return comment_lines, comment_token
744
+ end
745
+
746
+ def self.generate(options = {})
747
+ self.new(options).generate
748
+ end
749
+
750
+ require "ffi_gen/ruby_output"
751
+ require "ffi_gen/java_output"
752
+ end
753
+
754
+ if __FILE__ == $0
755
+ FFIGen.generate(
756
+ module_name: "FFIGen::Clang",
757
+ ffi_lib: ["libclang-3.5.so.1", "libclang.so.1", "clang"],
758
+ headers: ["clang-c/CXErrorCode.h", "clang-c/CXString.h", "clang-c/Index.h"],
759
+ cflags: `llvm-config --cflags`.split(" "),
760
+ prefixes: ["clang_", "CX"],
761
+ output: File.join(File.dirname(__FILE__), "ffi_gen/clang.rb")
762
+ )
763
+ end