Class: FFIGen

Inherits:
Object
  • Object
show all
Defined in:
lib/ffi_gen.rb,
lib/ffi_gen/java_output.rb,
lib/ffi_gen/ruby_output.rb

Defined Under Namespace

Modules: Clang Classes: ArrayType, ByReferenceType, ByValueType, Define, Enum, FunctionOrCallback, Name, PointerType, PrimitiveType, StringType, StructOrUnion, Type, UnknownType, Writer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ FFIGen

Returns a new instance of FFIGen.



280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/ffi_gen.rb', line 280

def initialize(options = {})
  @module_name   = options[:module_name] or fail "No module name given."
  @ffi_lib       = options.fetch :ffi_lib, nil
  @headers       = options[:headers] or fail "No headers given."
  @cflags        = options.fetch :cflags, []
  @prefixes      = options.fetch :prefixes, []
  @suffixes      = options.fetch :suffixes, []
  @blocking      = options.fetch :blocking, []
  @ffi_lib_flags = options.fetch :ffi_lib_flags, nil
  @output        = options.fetch :output, $stdout

  @translation_unit = nil
  @declarations = nil
end

Instance Attribute Details

#cflagsObject (readonly)

Returns the value of attribute cflags.



278
279
280
# File 'lib/ffi_gen.rb', line 278

def cflags
  @cflags
end

#ffi_libObject (readonly)

Returns the value of attribute ffi_lib.



278
279
280
# File 'lib/ffi_gen.rb', line 278

def ffi_lib
  @ffi_lib
end

#headersObject (readonly)

Returns the value of attribute headers.



278
279
280
# File 'lib/ffi_gen.rb', line 278

def headers
  @headers
end

#module_nameObject (readonly)

Returns the value of attribute module_name.



278
279
280
# File 'lib/ffi_gen.rb', line 278

def module_name
  @module_name
end

#outputObject (readonly)

Returns the value of attribute output.



278
279
280
# File 'lib/ffi_gen.rb', line 278

def output
  @output
end

#prefixesObject (readonly)

Returns the value of attribute prefixes.



278
279
280
# File 'lib/ffi_gen.rb', line 278

def prefixes
  @prefixes
end

Class Method Details

.generate(options = {}) ⇒ Object



819
820
821
# File 'lib/ffi_gen.rb', line 819

def self.generate(options = {})
  self.new(options).generate
end

Instance Method Details

#declarationsObject



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/ffi_gen.rb', line 328

def declarations
  return @declarations unless @declarations.nil?

  header_files = []
  Clang.get_inclusions translation_unit, proc { |included_file, inclusion_stack, include_length, client_data|
    filename = Clang.get_file_name(included_file).to_s_and_dispose
    header_files << included_file if @headers.any? { |header| header.is_a?(Regexp) ? header =~ filename : filename.end_with?(header) }
  }, nil

  unit_cursor = Clang.get_translation_unit_cursor translation_unit
  declaration_cursors = Clang.get_children unit_cursor
  declaration_cursors.delete_if { |cursor| [:macro_expansion, :inclusion_directive, :var_decl].include? cursor[:kind] }
  declaration_cursors.delete_if { |cursor| !header_files.include?(Clang.get_spelling_location_data(Clang.get_cursor_location(cursor))[:file]) }

  is_nested_declaration = []
  min_offset = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration_cursors.last))[:offset]
  declaration_cursors.reverse_each do |declaration_cursor|
    offset = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration_cursor))[:offset]
    is_nested_declaration.unshift(offset > min_offset)
    min_offset = offset if offset < min_offset
  end

  @declarations = []
  @declarations_by_name = {}
  @declarations_by_type = {}
  previous_declaration_end = Clang.get_cursor_location unit_cursor
  declaration_cursors.each_with_index do |declaration_cursor, index|
    comment = []
    unless is_nested_declaration[index]
      comment_range = Clang.get_range previous_declaration_end, Clang.get_cursor_location(declaration_cursor)
      comment, _ = extract_comment translation_unit, comment_range
      previous_declaration_end = Clang.get_range_end Clang.get_cursor_extent(declaration_cursor)
    end

    read_declaration declaration_cursor, comment
  end

  @declarations
end

#extract_comment(translation_unit, range, search_backwards = true) ⇒ Object



792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
# File 'lib/ffi_gen.rb', line 792

def extract_comment(translation_unit, range, search_backwards = true)
  tokens = Clang.get_tokens translation_unit, range

  iterator = search_backwards ? tokens.reverse_each : tokens.each
  comment_lines = []
  comment_token = nil
  comment_block = false
  iterator.each do |token|
    next if Clang.get_token_kind(token) != :comment
    comment = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
    lines = comment.split("\n").map { |line|
      line.sub!(/\ ?\*+\/\s*$/, '')
      line.sub!(/^\s*\/?[*\/]+ ?/, '')
      line.gsub!(/\\(brief|determine) /, '')
      line.gsub!('[', '(')
      line.gsub!(']', ')')
      line
    }
    comment_lines = lines + comment_lines
    comment_token = token
    comment_block = !comment_block if comment == "///"
    break unless comment_block and search_backwards
  end

  return comment_lines, comment_token
end

#generateObject



295
296
297
298
299
300
301
302
303
# File 'lib/ffi_gen.rb', line 295

def generate
  code = send "generate_#{File.extname(@output)[1..-1]}"
  if @output.is_a? String
    File.open(@output, "w") { |file| file.write code }
    puts "ffi_gen: #{@output}"
  else
    @output.write code
  end
end

#generate_javaObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/ffi_gen/java_output.rb', line 2

def generate_java
  writer = Writer.new "    ", " * ", "/**", " */"
  writer.puts "// Generated by ffi_gen. Please do not change this file by hand.", "import java.util.*;", "import com.sun.jna.*;", "import java.lang.annotation.*;", "import java.lang.reflect.Method;", "", "public class #{@module_name} {"
  writer.indent do
    writer.puts "private static #{@module_name}Interface INSTANCE = #{@module_name}Interface.JnaInstanceCreator.createInstance();"
    writer.puts "", *IO.readlines(File.join(File.dirname(__FILE__), "java_static.java")).map(&:rstrip), ""

    declarations.each do |declaration|
      if declaration.respond_to? :write_static_java
        declaration.write_static_java writer
      else
        declaration.write_java writer
      end
    end
    writer.puts ""

    writer.puts "interface #{@module_name}Interface extends Library {"
    writer.indent do
      writer.puts "", *IO.readlines(File.join(File.dirname(__FILE__), "java_interface.java")).map(&:rstrip), ""
      writer.puts "static class JnaInstanceCreator {"
      writer.indent do
        writer.puts "private static #{@module_name}Interface createInstance() {"
        writer.indent do
          writer.puts "DefaultTypeMapper typeMapper = new DefaultTypeMapper();", "typeMapper.addFromNativeConverter(NativeEnum.class, new EnumConverter());", "typeMapper.addToNativeConverter(NativeEnum.class, new EnumConverter());", ""
          writer.puts "Map<String, Object> options = new HashMap<String, Object>();", "options.put(Library.OPTION_FUNCTION_MAPPER, new NativeNameAnnotationFunctionMapper());", "options.put(Library.OPTION_TYPE_MAPPER, typeMapper);", ""
          writer.puts "return (#{@module_name}Interface) Native.loadLibrary(\"#{@ffi_lib}\", #{@module_name}Interface.class, options);"
        end
        writer.puts "}"
      end
      writer.puts "}", ""
      declarations.each do |declaration|
        if declaration.is_a? FunctionOrCallback
          declaration.write_java writer
        end
      end
    end
    writer.puts "}"
  end
  writer.puts "}"
  writer.output
end

#generate_rbObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# File 'lib/ffi_gen/ruby_output.rb', line 2

def generate_rb
  writer = Writer.new "  ", "# "
  writer.puts "# Generated by ffi_gen. Please do not change this file by hand.", "", "require 'ffi'", "", "module #{@module_name}"
  writer.indent do
    writer.puts "extend FFI::Library"
    writer.puts "ffi_lib_flags #{@ffi_lib_flags.map(&:inspect).join(', ')}" if @ffi_lib_flags
    writer.puts "ffi_lib #{@ffi_lib.inspect}", "" if @ffi_lib
    writer.puts "def self.attach_function(name, *_)", "  begin; super; rescue FFI::NotFoundError => e", "    (class << self; self; end).class_eval { define_method(name) { |*_| raise e } }", "  end", "end", ""
    declarations.each do |declaration|
      declaration.write_ruby writer
    end
  end
  writer.puts "end"
  writer.output
end

#get_pointee_declaration(type) ⇒ Object



784
785
786
787
788
789
790
# File 'lib/ffi_gen.rb', line 784

def get_pointee_declaration(type)
  canonical_type = Clang.get_canonical_type type
  return nil if canonical_type[:kind] != :pointer
  pointee_type = Clang.get_pointee_type canonical_type
  return nil if pointee_type[:kind] != :record
  @declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
end

#read_declaration(declaration_cursor, comment) ⇒ Object



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
# File 'lib/ffi_gen.rb', line 368

def read_declaration(declaration_cursor, comment)
  name = read_name declaration_cursor

  declaration = case declaration_cursor[:kind]
  when :enum_decl
    enum_description = []
    constant_descriptions = {}
    current_description = enum_description
    comment.each do |line|
      if line.gsub!(/@(.*?): /, '')
        current_description = []
        constant_descriptions[$1] = current_description
      end
      current_description = enum_description if line.strip.empty?
      current_description << line
    end

    constants = []
    previous_constant_location = Clang.get_cursor_location declaration_cursor
    next_constant_value = 0
    Clang.get_children(declaration_cursor).each do |enum_constant|
      constant_name = read_name enum_constant
      next if constant_name.nil?

      constant_location = Clang.get_cursor_location enum_constant
      constant_comment_range = Clang.get_range previous_constant_location, constant_location
      constant_description, _ = extract_comment translation_unit, constant_comment_range
      constant_description.concat(constant_descriptions[constant_name.raw] || [])
      previous_constant_location = constant_location

      begin
        value_cursor = Clang.get_children(enum_constant).first
        constant_value = if value_cursor
          parts = []
          Clang.get_tokens(translation_unit, Clang.get_cursor_extent(value_cursor)).each do |token|
            spelling = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose
            case Clang.get_token_kind(token)
            when :literal
              parts << spelling
            when :punctuation
              case spelling
              when "+", "-", "<<", ">>", "(", ")"
                parts << spelling
              else
                raise ArgumentError
              end
            else
              raise ArgumentError
            end
          end
          eval parts.join
        else
          next_constant_value
        end

        constants << { name: constant_name, value: constant_value, comment: constant_description }
        next_constant_value = constant_value + 1
      rescue ArgumentError
        puts "Warning: Could not process value of enum constant \"#{constant_name.raw}\""
      end
    end

    Enum.new self, name, constants, enum_description

  when :struct_decl, :union_decl
    @declarations_by_type[Clang.get_cursor_type(declaration_cursor)] ||= StructOrUnion.new(self, name, (declaration_cursor[:kind] == :union_decl))
    struct = @declarations_by_type[Clang.get_cursor_type(declaration_cursor)]
    raise if not struct.fields.empty?
    struct.description.concat comment

    struct_children = Clang.get_children declaration_cursor
    previous_field_end = Clang.get_cursor_location declaration_cursor
    last_nested_declaration = nil
    until struct_children.empty?
      child = struct_children.shift
      case child[:kind]
      when :struct_decl, :union_decl
        last_nested_declaration = read_declaration child, []
      when :field_decl
        field_name = read_name child
        field_extent = Clang.get_cursor_extent child

        field_comment_range = Clang.get_range previous_field_end, Clang.get_range_start(field_extent)
        field_comment, _ = extract_comment translation_unit, field_comment_range

        # check for comment starting on same line
        next_field_start = struct_children.first ? Clang.get_cursor_location(struct_children.first) : Clang.get_range_end(Clang.get_cursor_extent(declaration_cursor))
        following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start
        following_comment, following_comment_token = extract_comment translation_unit, following_comment_range, false
        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]
          field_comment = following_comment
          previous_field_end = Clang.get_range_end Clang.get_token_extent(translation_unit, following_comment_token)
        else
          previous_field_end = Clang.get_range_end field_extent
        end

        field_type = resolve_type Clang.get_cursor_type(child)
        last_nested_declaration.name ||= Name.new(name.parts + field_name.parts) if last_nested_declaration
        last_nested_declaration = nil
        struct.fields << { name: field_name, type: field_type, comment: field_comment }
      when :unexposed_attr
      else
        raise
      end
    end

    struct

  when :function_decl
    function_description = []
    return_value_description = []
    parameter_descriptions = {}
    current_description = function_description
    comment.each do |line|
      if line.gsub!(/\\param (.*?) /, '')
        current_description = []
        parameter_descriptions[$1] = current_description
      end
      current_description = return_value_description if line.gsub! '\\returns ', ''
      current_description << line
    end

    return_type = resolve_type Clang.get_cursor_result_type(declaration_cursor)
    parameters = []
    first_parameter_type = nil
    Clang.get_children(declaration_cursor).each do |function_child|
      next if function_child[:kind] != :parm_decl
      param_name = read_name function_child
      tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
      is_array = tokens.any? { |t| Clang.get_token_spelling(translation_unit, t).to_s_and_dispose == "[" }
      param_type = resolve_type Clang.get_cursor_type(function_child), is_array
      param_name ||= param_type.name
      param_name ||= Name.new []
      first_parameter_type ||= Clang.get_cursor_type function_child
      parameters << { name: param_name, type: param_type }
    end

    parameters.each_with_index do |parameter, index|
      parameter[:description] = parameter[:name] && parameter_descriptions[parameter[:name].raw]
      parameter[:description] ||= parameter_descriptions.values[index] if parameter_descriptions.size == parameters.size # workaround for wrong names
      parameter[:description] ||= []
    end

    function = FunctionOrCallback.new self, name, parameters, return_type, false, @blocking.include?(name.raw), function_description, return_value_description

    pointee_declaration = first_parameter_type && get_pointee_declaration(first_parameter_type)
    if pointee_declaration
      type_prefix = pointee_declaration.name.parts.join.downcase
      function_name_parts = name.parts.dup
      while type_prefix.start_with? function_name_parts.first.downcase
        type_prefix = type_prefix[function_name_parts.first.size..-1]
        function_name_parts.shift
        break if function_name_parts.empty?
      end
      if type_prefix.empty?
        pointee_declaration.oo_functions << [Name.new(function_name_parts), function]
      end
    end

    function

  when :typedef_decl
    typedef_children = Clang.get_children declaration_cursor
    underlying = Clang.get_typedef_decl_underlying_type(declaration_cursor)
    function_proto = Clang.get_canonical_type(Clang.get_pointee_type(underlying))
    if function_proto[:kind] == :function_proto || function_proto[:kind] == :function_no_proto

      function_description = []
      return_value_description = []
      parameter_descriptions = {}
      current_description = function_description
      comment.each do |line|
        if line.gsub!(/\\param (.*?) /, '')
          current_description = []
          parameter_descriptions[$1] = current_description
        end
        current_description = return_value_description if line.gsub! '\\returns ', ''
        current_description << line
      end

      return_type = resolve_type Clang.get_result_type(function_proto)
      parameters = []
      Clang.get_children(declaration_cursor).each do |function_child|
        next if function_child[:kind] != :parm_decl
        param_name = read_name function_child
        tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(function_child)
        is_array = tokens.any? { |t| Clang.get_token_spelling(translation_unit, t).to_s_and_dispose == "[" }
        param_type = resolve_type Clang.get_cursor_type(function_child), is_array
        param_name ||= param_type.name
        param_name ||= Name.new []
        first_parameter_type ||= Clang.get_cursor_type function_child
        parameters << { name: param_name, type: param_type }
      end

      parameters.each_with_index do |parameter, index|
        parameter[:description] = parameter[:name] && parameter_descriptions[parameter[:name].raw]
        parameter[:description] ||= parameter_descriptions.values[index] if parameter_descriptions.size == parameters.size # workaround for wrong names
        parameter[:description] ||= []
      end

      function = FunctionOrCallback.new self, name, parameters, return_type, true, false, function_description, return_value_description

      pointee_declaration = first_parameter_type && get_pointee_declaration(first_parameter_type)
      if pointee_declaration
        type_prefix = pointee_declaration.name.parts.join.downcase
        function_name_parts = name.parts.dup
        while type_prefix.start_with? function_name_parts.first.downcase
          type_prefix = type_prefix[function_name_parts.first.size..-1]
          function_name_parts.shift
          break if function_name_parts.empty?
        end
        if type_prefix.empty?
          pointee_declaration.oo_functions << [Name.new(function_name_parts), function]
        end
      end

      function
    elsif typedef_children.size == 1
      child_declaration = @declarations_by_type[Clang.get_cursor_type(typedef_children.first)]
      child_declaration.name = name if child_declaration and child_declaration.name.nil?
      nil
    elsif typedef_children.size > 1
      return_type = resolve_type Clang.get_cursor_type(typedef_children.first)
      parameters = []
      typedef_children.each do |param_decl|
        param_name = read_name param_decl
        param_type = resolve_type Clang.get_cursor_type(param_decl)
        param_name ||= param_type.name
        parameters << { name:param_name, type: param_type, description: [] }
      end
      FunctionOrCallback.new self, name, parameters, return_type, true, false, comment, []
    else
      nil
    end

  when :macro_definition
    tokens = Clang.get_tokens(translation_unit, Clang.get_cursor_extent(declaration_cursor)).map { |token|
      [Clang.get_token_kind(token), Clang.get_token_spelling(translation_unit, token).to_s_and_dispose]
    }
    if tokens.size > 1
      tokens.shift
      begin
        parameters = nil
        if tokens.first[1] == "("
          tokens_backup = tokens.dup
          begin
            parameters = []
            tokens.shift
            loop do
              kind, spelling = tokens.shift
              case kind
              when :identifier
                parameters << spelling
              when :punctuation
                break if spelling == ")"
                raise ArgumentError unless spelling == ","
              else
                raise ArgumentError
              end
            end
          rescue ArgumentError
            parameters = nil
            tokens = tokens_backup
          end
        end
        value = []
        until tokens.empty?
          kind, spelling = tokens.shift
          case kind
          when :literal
            value << spelling
          when :punctuation
            case spelling
            when "+", "-", "<<", ">>", ")"
              value << spelling
            when ","
              value << ", "
            when "("
              if tokens[1][1] == ")"
                tokens.delete_at 1
              else
                value << spelling
              end
            else
              raise ArgumentError
            end
          when :identifier
            raise ArgumentError unless parameters
            if parameters.include? spelling
              value << spelling
            elsif spelling == "NULL"
              value << "nil"
            else
              if not tokens.empty? and tokens.first[1] == "("
                tokens.shift
                if spelling == "strlen"
                  argument_kind, argument_spelling = tokens.shift
                  second_token_kind, second_token_spelling = tokens.shift
                  raise ArgumentError unless argument_kind == :identifier and second_token_spelling == ")"
                  value << "#{argument_spelling}.length"
                else
                  value << [:method, read_name(spelling)]
                  value << "("
                end
              else
                value << [:constant, read_name(spelling)]
              end
            end
          when :keyword
            raise ArgumentError unless spelling == "sizeof" and tokens[0][1] == "(" and tokens[1][0] == :literal and tokens[2][1] == ")"
            tokens.shift
            argument_kind, argument_spelling = tokens.shift
            value << "#{argument_spelling}.length"
            tokens.shift
          else
            raise ArgumentError
          end
        end
        Define.new(self, name, parameters, value)
      rescue ArgumentError
        puts "Warning: Could not process value of macro \"#{name.raw}\""
        nil
      end
    else
      nil
    end

  else
    raise declaration_cursor[:kind].to_s

  end

  return nil if declaration.nil?
  @declarations.delete declaration
  @declarations << declaration
  @declarations_by_name[name] = name.raw unless name.nil?
  type = Clang.get_cursor_type declaration_cursor
  @declarations_by_type[type] = declaration unless type.nil?

  declaration
end

#read_name(source) ⇒ Object



775
776
777
778
779
780
781
782
# File 'lib/ffi_gen.rb', line 775

def read_name(source)
  source = Clang.get_cursor_spelling(source).to_s_and_dispose if source.is_a? Clang::Cursor
  return nil if source.empty?
  trimmed = source.sub(/^(#{@prefixes.join('|')})/, '')
  trimmed = trimmed.sub(/(#{@suffixes.join('|')})$/, '')
  parts = trimmed.split(/_|(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).reject(&:empty?)
  Name.new parts, source
end

#resolve_type(full_type, is_array = false) ⇒ Object



710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
# File 'lib/ffi_gen.rb', line 710

def resolve_type(full_type, is_array = false)
  canonical_type = Clang.get_canonical_type full_type
  data_array = case canonical_type[:kind]
  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
    PrimitiveType.new canonical_type[:kind], Clang.get_type_spelling(full_type).to_s_and_dispose
  when :pointer
    if is_array
      ArrayType.new resolve_type(Clang.get_pointee_type(canonical_type)), nil
    else
      pointee_type = Clang.get_pointee_type canonical_type
      type = case pointee_type[:kind]
      when :char_s
        StringType.new
      when :record
        type = @declarations_by_type[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))]
        type &&= ByReferenceType.new(type)
      when :function_proto, :function_no_proto
        @declarations_by_type[full_type]
      else
        nil
      end

      if type.nil?
        pointer_depth = 0
        pointee_name = ""
        current_type = full_type
        loop do
          declaration_cursor = Clang.get_type_declaration current_type
          pointee_name = read_name declaration_cursor
          break if pointee_name

          case current_type[:kind]
          when :pointer
            pointer_depth += 1
            current_type = Clang.get_pointee_type current_type
          when :unexposed
            break
          else
            pointee_name = Name.new Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose.split("_")
            break
          end
        end
        pointee_type = resolve_type pointee_type
        type = PointerType.new pointee_type, pointee_name, pointer_depth
      end

      type
    end
  when :record
    type = @declarations_by_type[canonical_type]
    type &&= ByValueType.new(type)
    type || UnknownType.new # TODO
  when :enum
    @declarations_by_type[canonical_type] || UnknownType.new # TODO
  when :constant_array
    ArrayType.new resolve_type(Clang.get_array_element_type(canonical_type)), Clang.get_array_size(canonical_type)
  when :unexposed, :function_proto, :function_no_proto
    UnknownType.new
  when :incomplete_array
    ArrayType.new resolve_type(Clang.get_array_element_type(canonical_type)), nil
  else
    raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}"
  end
end

#translation_unitObject



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/ffi_gen.rb', line 305

def translation_unit
  return @translation_unit unless @translation_unit.nil?

  args = []
  @headers.each do |header|
    args.push "-include", header unless header.is_a? Regexp
  end
  args.concat @cflags
  args_ptr = FFI::MemoryPointer.new :pointer, args.size
  pointers = args.map { |arg| FFI::MemoryPointer.from_string arg }
  args_ptr.write_array_of_pointer pointers

  index = Clang.create_index 0, 0
  @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]

  Clang.get_num_diagnostics(@translation_unit).times do |i|
    diag = Clang.get_diagnostic @translation_unit, i
    $stderr.puts Clang.format_diagnostic(diag, Clang.default_diagnostic_display_options).to_s_and_dispose
  end

  @translation_unit
end