Class: FFIGen
- Inherits:
-
Object
- Object
- FFIGen
- Defined in:
- lib/ffi_gen.rb
Defined Under Namespace
Classes: Constant, Enum, Function, Struct, Writer
Constant Summary collapse
- RUBY_KEYWORDS =
%w{alias allocate and begin break case class def defined do else elsif end ensure false for if in initialize module next nil not or redo rescue retry return self super then true undef unless until when while yield}
Instance Attribute Summary collapse
-
#blacklist ⇒ Object
readonly
Returns the value of attribute blacklist.
-
#cflags ⇒ Object
readonly
Returns the value of attribute cflags.
-
#ffi_lib ⇒ Object
readonly
Returns the value of attribute ffi_lib.
-
#headers ⇒ Object
readonly
Returns the value of attribute headers.
-
#output ⇒ Object
readonly
Returns the value of attribute output.
-
#ruby_module ⇒ Object
readonly
Returns the value of attribute ruby_module.
Class Method Summary collapse
Instance Method Summary collapse
- #declarations ⇒ Object
- #extract_comment(translation_unit, range) ⇒ Object
- #generate ⇒ Object
-
#initialize(options = {}) ⇒ FFIGen
constructor
A new instance of FFIGen.
- #read_named_declaration(declaration, name, comment) ⇒ Object
- #to_ffi_type(full_type) ⇒ Object
- #to_ruby_camelcase(str) ⇒ Object
- #to_ruby_lowercase(str, avoid_keywords = false) ⇒ Object
- #to_type_name(full_type, short = false) ⇒ Object
- #translation_unit ⇒ Object
Constructor Details
#initialize(options = {}) ⇒ FFIGen
Returns a new instance of FFIGen.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/ffi_gen.rb', line 284 def initialize( = {}) @ruby_module = [:ruby_module] or fail "No module name given." @ffi_lib = [:ffi_lib] or fail "No FFI library given." @headers = [:headers] or fail "No headers given." @cflags = .fetch :cflags, [] @prefixes = .fetch :prefixes, [] @blacklist = .fetch :blacklist, [] @output = .fetch :output, $stdout blacklist = @blacklist @blacklist = lambda { |name| blacklist.include? name } if @blacklist.is_a? Array @translation_unit = nil @declarations = nil end |
Instance Attribute Details
#blacklist ⇒ Object (readonly)
Returns the value of attribute blacklist.
282 283 284 |
# File 'lib/ffi_gen.rb', line 282 def blacklist @blacklist end |
#cflags ⇒ Object (readonly)
Returns the value of attribute cflags.
282 283 284 |
# File 'lib/ffi_gen.rb', line 282 def cflags @cflags end |
#ffi_lib ⇒ Object (readonly)
Returns the value of attribute ffi_lib.
282 283 284 |
# File 'lib/ffi_gen.rb', line 282 def ffi_lib @ffi_lib end |
#headers ⇒ Object (readonly)
Returns the value of attribute headers.
282 283 284 |
# File 'lib/ffi_gen.rb', line 282 def headers @headers end |
#output ⇒ Object (readonly)
Returns the value of attribute output.
282 283 284 |
# File 'lib/ffi_gen.rb', line 282 def output @output end |
#ruby_module ⇒ Object (readonly)
Returns the value of attribute ruby_module.
282 283 284 |
# File 'lib/ffi_gen.rb', line 282 def ruby_module @ruby_module end |
Class Method Details
.generate(options = {}) ⇒ Object
589 590 591 |
# File 'lib/ffi_gen.rb', line 589 def self.generate( = {}) self.new().generate end |
Instance Method Details
#declarations ⇒ Object
323 324 325 326 327 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 367 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 |
# File 'lib/ffi_gen.rb', line 323 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| filename.end_with? header } }, nil @declarations = {} unit_cursor = Clang.get_translation_unit_cursor translation_unit previous_declaration_end = Clang.get_cursor_location unit_cursor Clang.get_children(unit_cursor).each do |declaration| file_ptr = FFI::MemoryPointer.new :pointer Clang.get_spelling_location Clang.get_cursor_location(declaration), file_ptr, nil, nil, nil file = file_ptr.read_pointer extent = Clang.get_cursor_extent declaration comment_range = Clang.get_range previous_declaration_end, Clang.get_range_start(extent) unless declaration[:kind] == :enum_decl or declaration[:kind] == :struct_decl # keep comment for typedef_decl previous_declaration_end = Clang.get_range_end extent end next if not header_files.include? file name = Clang.get_cursor_spelling(declaration).to_s_and_dispose next if @blacklist[name] comment = extract_comment translation_unit, comment_range case declaration[:kind] when :enum_decl, :struct_decl read_named_declaration declaration, name, comment unless name.empty? when :function_decl function = Function.new self, name, false, comment function.return_type = Clang.get_cursor_result_type declaration @declarations[name] = function Clang.get_children(declaration).each do |function_child| next if function_child[:kind] != :parm_decl param_name = Clang.get_cursor_spelling(function_child).to_s_and_dispose param_type = Clang.get_cursor_type function_child function.parameters << { name: param_name, type: param_type} end when :typedef_decl typedef_children = Clang.get_children declaration if typedef_children.size == 1 read_named_declaration typedef_children.first, name, comment unless @declarations.has_key? name elsif typedef_children.size > 1 callback = Function.new self, name, true, comment callback.return_type = Clang.get_cursor_type typedef_children.first @declarations[name] = callback typedef_children[1..-1].each do |param_decl| param_name = Clang.get_cursor_spelling(param_decl).to_s_and_dispose param_type = Clang.get_cursor_type param_decl callback.parameters << { name: param_name, type: param_type } end end when :macro_definition tokens_ptr_ptr = FFI::MemoryPointer.new :pointer num_tokens_ptr = FFI::MemoryPointer.new :uint Clang.tokenize translation_unit, extent, tokens_ptr_ptr, num_tokens_ptr num_tokens = num_tokens_ptr.read_uint tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer if num_tokens == 3 token = Clang::Token.new tokens_ptr[1] if Clang.get_token_kind(token) == :literal value = Clang.get_token_spelling(translation_unit, token).to_s_and_dispose @declarations[name] = Constant.new self, name, value end end end end @declarations end |
#extract_comment(translation_unit, range) ⇒ Object
477 478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/ffi_gen.rb', line 477 def extract_comment(translation_unit, range) tokens_ptr_ptr = FFI::MemoryPointer.new :pointer num_tokens_ptr = FFI::MemoryPointer.new :uint Clang.tokenize translation_unit, range, tokens_ptr_ptr, num_tokens_ptr num_tokens = num_tokens_ptr.read_uint tokens_ptr = FFI::Pointer.new Clang::Token, tokens_ptr_ptr.read_pointer (num_tokens - 1).downto(0) do |i| token = Clang::Token.new tokens_ptr[i] return Clang.get_token_spelling(translation_unit, token).to_s_and_dispose if Clang.get_token_kind(token) == :comment end "" end |
#generate ⇒ Object
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/ffi_gen.rb', line 407 def generate writer = Writer.new writer.puts "# Generated by ffi_gen. Please do not change this file by hand.", "", "require 'ffi'", "", "module #{@ruby_module}" writer.indent do writer.puts "extend FFI::Library", "ffi_lib '#{@ffi_lib}'", "" declarations.each do |name, declaration| declaration.write writer end end writer.puts "end" if @output.is_a? String File.open(@output, "w") { |file| file.write writer.output } puts "ffi_gen: #{@output}" else @output.write writer.output end end |
#read_named_declaration(declaration, name, comment) ⇒ Object
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 |
# File 'lib/ffi_gen.rb', line 425 def read_named_declaration(declaration, name, comment) case declaration[:kind] when :enum_decl enum = Enum.new self, name, comment @declarations[name] = enum previous_constant_location = Clang.get_cursor_location declaration Clang.get_children(declaration).each do |enum_constant| constant_name = Clang.get_cursor_spelling(enum_constant).to_s_and_dispose constant_value = nil value_cursor = Clang.get_children(enum_constant).first constant_value = value_cursor && case value_cursor[:kind] when :integer_literal tokens_ptr_ptr = FFI::MemoryPointer.new :pointer num_tokens_ptr = FFI::MemoryPointer.new :uint Clang.tokenize translation_unit, Clang.get_cursor_extent(value_cursor), tokens_ptr_ptr, num_tokens_ptr token = Clang::Token.new tokens_ptr_ptr.read_pointer literal = Clang.get_token_spelling translation_unit, token Clang.dispose_tokens translation_unit, tokens_ptr_ptr.read_pointer, num_tokens_ptr.read_uint literal else next # skip those entries for now end constant_location = Clang.get_cursor_location enum_constant constant_comment_range = Clang.get_range previous_constant_location, constant_location constant_comment = extract_comment translation_unit, constant_comment_range previous_constant_location = constant_location enum.constants << { name: constant_name, value: constant_value, comment: constant_comment } end when :struct_decl struct = Struct.new self, name, comment @declarations[name] = struct previous_field_location = Clang.get_cursor_location declaration Clang.get_children(declaration).each do |field_decl| field_name = Clang.get_cursor_spelling(field_decl).to_s_and_dispose field_type = Clang.get_cursor_type field_decl field_location = Clang.get_cursor_location field_decl field_comment_range = Clang.get_range previous_field_location, field_location field_comment = extract_comment translation_unit, field_comment_range previous_field_location = field_location struct.fields << { name: field_name, type: field_type, comment: field_comment } end end end |
#to_ffi_type(full_type) ⇒ Object
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 |
# File 'lib/ffi_gen.rb', line 490 def to_ffi_type(full_type) declaration = Clang.get_type_declaration full_type name = Clang.get_cursor_spelling(declaration).to_s_and_dispose return @declarations[name].reference if @declarations.has_key? name canonical_type = Clang.get_canonical_type full_type case canonical_type[:kind] when :void then ":void" when :bool then ":bool" when :u_char then ":uchar" when :u_short then ":ushort" when :u_int then ":uint" when :u_long then ":ulong" when :u_long_long then ":ulong_long" when :char_s, :s_char then ":char" when :short then ":short" when :int then ":int" when :long then ":long" when :long_long then ":long_long" when :float then ":float" when :double then ":double" when :pointer pointee_type = Clang.get_pointee_type canonical_type pointee_type[:kind] == :char_s ? ":string" : ":pointer" when :constant_array element_type = Clang.get_array_element_type canonical_type size = Clang.get_array_size canonical_type "[#{to_ffi_type element_type}, #{size}]" else raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}" end end |
#to_ruby_camelcase(str) ⇒ Object
581 582 583 584 585 586 587 |
# File 'lib/ffi_gen.rb', line 581 def to_ruby_camelcase(str) str = str.dup str.sub! /^(#{@prefixes.join('|')})/, '' # remove prefixes str.gsub!(/(^|_)[a-z]/) { |match| match.upcase } # make word beginnings upcased str.gsub! '_', '' # remove all underscores str end |
#to_ruby_lowercase(str, avoid_keywords = false) ⇒ Object
568 569 570 571 572 573 574 575 576 577 578 579 |
# File 'lib/ffi_gen.rb', line 568 def to_ruby_lowercase(str, avoid_keywords = false) str = str.dup str.sub! /^(#{@prefixes.join('|')})/, '' # remove prefixes str.gsub! /([A-Z][a-z])/, '_\1' # add underscores before word beginnings str.gsub! /([a-z])([A-Z])/, '\1_\2' # add underscores after word endings str.sub! /^_*/, '' # remove underscores at the beginning str.gsub! /__+/, '_' # replace multiple underscores by only one str.downcase! str.sub! /^\d/, '_\0' # fix illegal beginnings str = "_#{str}" if avoid_keywords and RUBY_KEYWORDS.include? str str end |
#to_type_name(full_type, short = false) ⇒ Object
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 |
# File 'lib/ffi_gen.rb', line 523 def to_type_name(full_type, short = false) declaration = Clang.get_type_declaration full_type name = Clang.get_cursor_spelling(declaration).to_s_and_dispose return @declarations[name].type_name(short) if @declarations.has_key? name canonical_type = Clang.get_canonical_type full_type case canonical_type[:kind] when :void then "nil" when :bool then "Boolean" when :u_char, :u_short, :u_int, :u_long, :u_long_long, :char_s, :s_char, :short, :int, :long, :long_long then "Integer" when :float, :double then "Float" when :pointer pointee_type = Clang.get_pointee_type canonical_type if pointee_type[:kind] == :char_s "String" else pointer_depth = 0 pointer_target_name = "" current_type = full_type loop do declaration = Clang.get_type_declaration current_type pointer_target_name = to_ruby_camelcase Clang.get_cursor_spelling(declaration).to_s_and_dispose break if not pointer_target_name.empty? case current_type[:kind] when :pointer pointer_depth += 1 current_type = Clang.get_pointee_type current_type when :unexposed break else pointer_target_name = Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose break end end short ? pointer_target_name : "FFI::Pointer(#{'*' * pointer_depth}#{pointer_target_name})" end when :constant_array element_type = Clang.get_array_element_type canonical_type "Array<#{to_type_name element_type}>" else raise NotImplementedError, "No type name for type #{canonical_type[:kind]}" end end |
#translation_unit ⇒ Object
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/ffi_gen.rb', line 300 def translation_unit return @translation_unit unless @translation_unit.nil? args = [] @headers.each do |header| args.push "-include", header 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.).to_s_and_dispose end @translation_unit end |