ghazel-ffi_gen 1.3.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.
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