Class: FFIGen
- Inherits:
-
Object
- Object
- FFIGen
- Defined in:
- lib/ffi_gen.rb,
lib/ffi_gen/java_output.rb,
lib/ffi_gen/ruby_output.rb
Defined Under Namespace
Modules: Clang Classes: Constant, Enum, FunctionOrCallback, Name, StructOrUnion, Writer
Instance Attribute Summary collapse
-
#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.
-
#module_name ⇒ Object
readonly
Returns the value of attribute module_name.
-
#output ⇒ Object
readonly
Returns the value of attribute output.
-
#prefixes ⇒ Object
readonly
Returns the value of attribute prefixes.
Class Method Summary collapse
Instance Method Summary collapse
- #declarations ⇒ Object
- #extract_comment(translation_unit, range, search_backwards = true, return_spelling = true) ⇒ Object
- #generate ⇒ Object
- #generate_java ⇒ Object
- #generate_rb ⇒ Object
- #get_pointee_declaration(type) ⇒ Object
-
#initialize(options = {}) ⇒ FFIGen
constructor
A new instance of FFIGen.
- #read_named_declaration(declaration, comment) ⇒ Object
- #read_value(cursor) ⇒ Object
- #to_java_type(full_type, is_array = false) ⇒ Object
- #to_ruby_type(full_type) ⇒ Object
- #translation_unit ⇒ Object
Constructor Details
#initialize(options = {}) ⇒ FFIGen
Returns a new instance of FFIGen.
223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/ffi_gen.rb', line 223 def initialize( = {}) @module_name = [:module_name] 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, [] @blocking = .fetch :blocking, [] @ffi_lib_flags = .fetch :ffi_lib_flags, nil @output = .fetch :output, $stdout @translation_unit = nil @declarations = nil end |
Instance Attribute Details
#cflags ⇒ Object (readonly)
Returns the value of attribute cflags.
221 222 223 |
# File 'lib/ffi_gen.rb', line 221 def cflags @cflags end |
#ffi_lib ⇒ Object (readonly)
Returns the value of attribute ffi_lib.
221 222 223 |
# File 'lib/ffi_gen.rb', line 221 def ffi_lib @ffi_lib end |
#headers ⇒ Object (readonly)
Returns the value of attribute headers.
221 222 223 |
# File 'lib/ffi_gen.rb', line 221 def headers @headers end |
#module_name ⇒ Object (readonly)
Returns the value of attribute module_name.
221 222 223 |
# File 'lib/ffi_gen.rb', line 221 def module_name @module_name end |
#output ⇒ Object (readonly)
Returns the value of attribute output.
221 222 223 |
# File 'lib/ffi_gen.rb', line 221 def output @output end |
#prefixes ⇒ Object (readonly)
Returns the value of attribute prefixes.
221 222 223 |
# File 'lib/ffi_gen.rb', line 221 def prefixes @prefixes end |
Class Method Details
.generate(options = {}) ⇒ Object
474 475 476 |
# File 'lib/ffi_gen.rb', line 474 def self.generate( = {}) self.new().generate end |
Instance Method Details
#declarations ⇒ Object
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/ffi_gen.rb', line 270 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 @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 = Clang.get_spelling_location_data(Clang.get_cursor_location(declaration))[:file] extent = Clang.get_cursor_extent declaration comment_range = Clang.get_range previous_declaration_end, Clang.get_range_start(extent) unless [:enum_decl, :struct_decl, :union_decl].include? declaration[:kind] # keep comment for typedef_decl previous_declaration_end = Clang.get_range_end extent end next if not header_files.include? file comment = extract_comment translation_unit, comment_range read_named_declaration declaration, comment end @declarations end |
#extract_comment(translation_unit, range, search_backwards = true, return_spelling = true) ⇒ Object
463 464 465 466 467 468 469 470 471 472 |
# File 'lib/ffi_gen.rb', line 463 def extract_comment(translation_unit, range, search_backwards = true, return_spelling = true) tokens = Clang.get_tokens translation_unit, range iterator = search_backwards ? tokens.reverse_each : tokens.each iterator.each do |token| if Clang.get_token_kind(token) == :comment return return_spelling ? Clang.get_token_spelling(translation_unit, token).to_s_and_dispose : token end end "" end |
#generate ⇒ Object
237 238 239 240 241 242 243 244 245 |
# File 'lib/ffi_gen.rb', line 237 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_java ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 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 interface #{@module_name} extends Library {" writer.indent do writer.puts "", *IO.readlines(File.join(File.dirname(__FILE__), "java_pre.java")).map(&:rstrip) writer.puts "", "public static #{@module_name} INSTANCE = JnaInstanceCreator.createInstance();", "" writer.puts "static class JnaInstanceCreator {" writer.indent do writer.puts "private static #{@module_name} 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}) Native.loadLibrary(\"#{@ffi_lib}\", #{@module_name}.class, options);" end writer.puts "}" end writer.puts "}", "" declarations.values.compact.uniq.each do |declaration| declaration.write_java writer end end writer.puts "}" writer.output end |
#generate_rb ⇒ Object
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}'", "" 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.values.compact.uniq.each do |declaration| declaration.write_ruby writer end end writer.puts "end" writer.output end |
#get_pointee_declaration(type) ⇒ Object
455 456 457 458 459 460 461 |
# File 'lib/ffi_gen.rb', line 455 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[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))] end |
#read_named_declaration(declaration, comment) ⇒ Object
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 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 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/ffi_gen.rb', line 301 def read_named_declaration(declaration, comment) name = Name.new self, Clang.get_cursor_spelling(declaration).to_s_and_dispose case declaration[:kind] when :enum_decl enum = Enum.new self, name, comment @declarations[Clang.get_cursor_type(declaration)] = enum previous_constant_location = Clang.get_cursor_location declaration next_constant_value = 0 Clang.get_children(declaration).each do |enum_constant| constant_name = Name.new self, Clang.get_cursor_spelling(enum_constant).to_s_and_dispose 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 catch :unsupported_value do value_cursor = Clang.get_children(enum_constant).first constant_value = if value_cursor read_value value_cursor else next_constant_value end enum.constants << { name: constant_name, value: constant_value, comment: constant_comment } next_constant_value = constant_value + 1 end end when :struct_decl, :union_decl struct = @declarations.delete(Clang.get_cursor_type(declaration)) || StructOrUnion.new(self, name, (declaration[:kind] == :union_decl)) raise if not struct.fields.empty? struct.comment << "\n#{comment}" struct_children = Clang.get_children declaration previous_field_end = Clang.get_cursor_location declaration until struct_children.empty? nested_declaration = [:struct_decl, :union_decl].include?(struct_children.first[:kind]) ? struct_children.shift : nil field = struct_children.shift raise if field[:kind] != :field_decl field_name = Name.new self, Clang.get_cursor_spelling(field).to_s_and_dispose field_extent = Clang.get_cursor_extent field 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)) following_comment_range = Clang.get_range Clang.get_range_end(field_extent), next_field_start following_comment_token = extract_comment translation_unit, following_comment_range, false, 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 = Clang.get_token_spelling(translation_unit, following_comment_token).to_s_and_dispose 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 if nested_declaration read_named_declaration nested_declaration, "" decl = @declarations[Clang.get_cursor_type(nested_declaration)] decl.name = Name.new(self, name.parts + field_name.parts) if decl and decl.name.empty? end field_type = Clang.get_cursor_type field struct.fields << { name: field_name, type: field_type, comment: field_comment } end @declarations[Clang.get_cursor_type(declaration)] = struct when :function_decl function = FunctionOrCallback.new self, name, false, @blocking.include?(name.raw), comment function.return_type = Clang.get_cursor_result_type declaration @declarations[declaration] = function Clang.get_children(declaration).each do |function_child| next if function_child[:kind] != :parm_decl param_name = Name.new self, Clang.get_cursor_spelling(function_child).to_s_and_dispose param_type = Clang.get_cursor_type 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 == "[" } function.parameters << { name: param_name, type: param_type, is_array: is_array } end pointee_declaration = function.parameters.first && get_pointee_declaration(function.parameters.first[: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 end if type_prefix.empty? pointee_declaration.oo_functions << [Name.new(self, function_name_parts), function, get_pointee_declaration(function.return_type)] end end when :typedef_decl typedef_children = Clang.get_children declaration if typedef_children.size == 1 child_declaration = @declarations[Clang.get_cursor_type(typedef_children.first)] child_declaration.name = name if child_declaration and child_declaration.name.empty? elsif typedef_children.size > 1 callback = FunctionOrCallback.new self, name, true, false, comment callback.return_type = Clang.get_cursor_type typedef_children.first @declarations[Clang.get_cursor_type(declaration)] = callback typedef_children[1..-1].each do |param_decl| param_name = Name.new self, 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 = Clang.get_tokens translation_unit, Clang.get_cursor_extent(declaration) if tokens.size == 3 if Clang.get_token_kind(tokens[1]) == :literal value = Clang.get_token_spelling(translation_unit, tokens[1]).to_s_and_dispose value.sub!(/[A-Za-z]+$/, '') unless value.start_with? '0x' # remove number suffixes @declarations[name] ||= Constant.new self, name, value end end end end |
#read_value(cursor) ⇒ Object
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 |
# File 'lib/ffi_gen.rb', line 431 def read_value(cursor) parts = [] tokens = Clang.get_tokens translation_unit, Clang.get_cursor_extent(cursor) tokens.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 ",", "}" # ignored when "+", "-", "<<", ">>" parts << spelling else throw :unsupported_value end else throw :unsupported_value end end eval parts.join end |
#to_java_type(full_type, is_array = false) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/ffi_gen/java_output.rb', line 27 def to_java_type(full_type, is_array = false) canonical_type = Clang.get_canonical_type full_type data_array = case canonical_type[:kind] when :void then ["void", "nil"] when :bool then ["boolean", "Boolean"] when :u_char then ["byte", "Integer"] when :u_short then ["short", "Integer"] when :u_int then ["int", "Integer"] when :u_long then ["NativeLong", "Integer"] when :u_long_long then ["long", "Integer"] when :char_s, :s_char then ["byte", "Integer"] when :short then ["short", "Integer"] when :int then ["int", "Integer"] when :long then ["NativeLong", "Integer"] when :long_long then ["long", "Integer"] when :float then ["float", "Float"] when :double then ["double", "Float"] when :pointer if is_array element_type = to_java_type Clang.get_pointee_type(canonical_type) return { jna_type: "#{element_type[:jna_type]}[]", description: "Array of #{element_type[:description]}", parameter_name: element_type[:parameter_name] } end pointee_type = Clang.get_pointee_type canonical_type result = nil case pointee_type[:kind] when :char_s result = ["String", "String"] when :record pointee_declaration = @declarations[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))] result = [pointee_declaration.java_name, pointee_declaration.java_name] if pointee_declaration and pointee_declaration.written when :function_proto declaration = @declarations[full_type] result = [":#{declaration.java_name}", "Proc(_callback_#{declaration.java_name}_)"] if declaration end if result.nil? pointer_depth = 0 pointer_target_name = "" current_type = full_type loop do declaration = Clang.get_type_declaration current_type pointer_target_name = Name.new self, 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 = Name.new self, Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose break end end result = ["Pointer", "FFI::Pointer(#{'*' * pointer_depth}#{pointer_target_name.to_java_classname})", pointer_target_name] end result when :record declaration = @declarations[canonical_type] declaration ? ["#{declaration.java_name}.by_value", declaration.java_name] : ["byte", "unknown"] # TODO when :enum declaration = @declarations[canonical_type] declaration ? [declaration.java_name, "Symbol from _enum_#{declaration.java_name}_", declaration.name] : ["byte", "unknown"] # TODO when :constant_array element_type_data = to_java_type Clang.get_array_element_type(canonical_type) size = Clang.get_array_size canonical_type ["[#{element_type_data[:jna_type]}, #{size}]", "Array<#{element_type_data[:description]}>"] else raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}" end { jna_type: data_array[0], description: data_array[1], parameter_name: (data_array[2] || Name.new(self, data_array[1])).to_java_downcase } end |
#to_ruby_type(full_type) ⇒ Object
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/ffi_gen/ruby_output.rb', line 18 def to_ruby_type(full_type) canonical_type = Clang.get_canonical_type full_type data_array = case canonical_type[:kind] when :void then [":void", "nil"] when :bool then [":bool", "Boolean"] when :u_char then [":uchar", "Integer"] when :u_short then [":ushort", "Integer"] when :u_int then [":uint", "Integer"] when :u_long then [":ulong", "Integer"] when :u_long_long then [":ulong_long", "Integer"] when :char_s, :s_char then [":char", "Integer"] when :short then [":short", "Integer"] when :int then [":int", "Integer"] when :long then [":long", "Integer"] when :long_long then [":long_long", "Integer"] when :float then [":float", "Float"] when :double then [":double", "Float"] when :pointer pointee_type = Clang.get_pointee_type canonical_type result = nil case pointee_type[:kind] when :char_s result = [":string", "String"] when :record pointee_declaration = @declarations[Clang.get_cursor_type(Clang.get_type_declaration(pointee_type))] result = [pointee_declaration.ruby_name, pointee_declaration.ruby_name] if pointee_declaration and pointee_declaration.written when :function_proto declaration = @declarations[full_type] result = [":#{declaration.ruby_name}", "Proc(_callback_#{declaration.ruby_name}_)"] if declaration end if result.nil? pointer_depth = 0 pointer_target_name = "" current_type = full_type loop do declaration = Clang.get_type_declaration current_type pointer_target_name = Name.new self, 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 = Name.new self, Clang.get_type_kind_spelling(current_type[:kind]).to_s_and_dispose break end end result = [":pointer", "FFI::Pointer(#{'*' * pointer_depth}#{pointer_target_name.to_ruby_classname})", pointer_target_name] end result when :record declaration = @declarations[canonical_type] declaration ? ["#{declaration.ruby_name}.by_value", declaration.ruby_name] : [":char", "unknown"] # TODO when :enum declaration = @declarations[canonical_type] declaration ? [":#{declaration.ruby_name}", "Symbol from _enum_#{declaration.ruby_name}_", declaration.name] : [":char", "unknown"] # TODO when :constant_array element_type_data = to_ruby_type Clang.get_array_element_type(canonical_type) size = Clang.get_array_size canonical_type ["[#{element_type_data[:ffi_type]}, #{size}]", "Array<#{element_type_data[:description]}>"] when :unexposed [":char", "unexposed"] else raise NotImplementedError, "No translation for values of type #{canonical_type[:kind]}" end { ffi_type: data_array[0], description: data_array[1], parameter_name: (data_array[2] || Name.new(self, data_array[1])).to_ruby_downcase } end |
#translation_unit ⇒ Object
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/ffi_gen.rb', line 247 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.).to_s_and_dispose end @translation_unit end |