require 'rubygems' require_gem 'ruby-breakpoint' require_gem 'cast' require 'open3' # This is generally useful # class String def pad(to_len, with = ' ') pad = '' self.length.upto(to_len) do pad << with end return pad << self end end class Autobindings # Outputs a little prettier headings # def title(text, glyph = '-', size = 65) '#' + ''.pad(size, glyph) + "\n" + '# ' + text + "\n" + '#' + ''.pad(size, glyph) + "\n" end # Setup our defaults # def initialize() @gcc_cmd = '/usr/bin/gcc' @gcc_params = '-E -P -w' @gcc_extra_params = '' @debug = false @debug_lines = false @graph = false @headers_path = '' @headers = [] @config = {} @handmade = nil end # Various parameters. See documentation for exact meaning # attr_accessor :handmade, :gcc_cmd, :gcc_extra_params, :headers_path, :debug, :debug_lines, :graph attr_reader :config def config=(cfg) @config = cfg @config[:external_types] = [] if @config[:external_types].nil? @config[:type_aliases] = {} if @config[:type_aliases].nil? @config[:gcc_params] = '' if @config[:gcc_params].nil? end # Feed the header files to gcc for preprocessing, read the output and strip linemarkers # def run_preprocessor if @debug then puts title( "Preprocessing header files" ) puts "Preprocessor: #{@gcc_cmd}" puts "Headers directory: #{@headers_path}" puts "Headers that will be processed: #{@config[:headers].join(', ')}" puts "Extra options from commandline: #{@gcc_extra_params}" puts "Extra options from config file: #{@config[:gcc_params]}" end prevcur = Dir.pwd Dir.chdir(@headers_path) # move to the headers dir, so we can pass just to names to the preprocessor instead of long paths params = [@gcc_params, @gcc_extra_params, @config[:gcc_params]].join(' ') cmd = [@gcc_cmd, params, @config[:headers].join(' ')].join(' ') puts "Resulting command line is below:\n-- #{cmd}" if @debug @data = '' gcc_err = '' Open3.popen3(cmd) { |stdin, stdout, stderr| stdout.read.each_line { |line| next if line[0].chr == '#' line = line.gsub(/__extension__/, '') # GCC extensions choke the C parser, this is the only way i have to remove them @data << line } gcc_err = stderr.read } puts gcc_err if @debug Dir.chdir(prevcur) # get back where we were, you never know... if @debug_lines then puts title("Preprocessor output:") i = 1 part = '' @data.each_line { |l| puts "[#{i.to_s.pad(3,'0')}] " << l; i+=1; } end end # parse all the data and extract only the information that is meaningful to our generator # def parse_data extra_types = @config[:external_types] extra_types = [] if extra_types.nil? if @debug then puts title( "Parsing C headers" ) puts "#{extra_types.length} extra types will be passed to the C parser:\n#{extra_types.join(', ')}" end parser = C::Parser.new parser.type_names.merge(extra_types) #~ puts "--------------------------------" #~ puts @data #~ puts "--------------------------------" tree = parser.parse(@data) @enums = {} @types = [] @decls = [] @structs = [] @protos = {} @synon = {} @all_synon = {} tree.entities.each { |e| if !(C::Declaration === e) puts "Skipping #{e.class.to_s}" if @debug next end if e.typedef? # check if this a typedef defining a pointer to function, which we will use for callbacks fp = FunctionPtr.parse(e) if !fp.nil? then @protos[fp.name] = fp.to_s(false, false) else name = e.type.to_s name << e.declarators[0].indirect_type.to_s if C::Pointer === e.declarators[0].indirect_type @all_synon[name] = e.declarators[0].name end end # -- ENUMERATION if C::Enum === e.type then if e.type.name.nil? puts "Skipping anonymous enum" if @debug next end next if e.type.members.nil? # the enum was already defined, this is just the typedef. todo: refactor into the typedef case @types << 'enum ' + e.type.name lv = 0 en = (@enums[e.type.name] = {}) e.type.members.each { |mem| if mem.val.nil? then en[mem.name] = lv.to_i lv += 1 else # todo: Yes, this is very very brittle. There are a lot more arithmetics that # someone can do inside an enum that would screw up this. However this is the most used stuff. # if C::BitOr === mem.val en[mem.name.to_s] = en[mem.val.expr1.to_s].to_i | en[mem.val.expr2.to_s].to_i elsif C::BitAnd === mem.val en[mem.name.to_s] = en[mem.val.expr1.to_s].to_i & en[mem.val.expr2.to_s].to_i elsif C::ShiftLeft === mem.val en[mem.name.to_s] = en[mem.val.expr1.to_s].to_i << en[mem.val.expr2.to_s].to_i elsif C::ShiftRight === mem.val en[mem.name.to_s] = en[mem.val.expr1.to_s].to_i >> en[mem.val.expr2.to_s].to_i elsif C::Negative === mem.val en[mem.name.to_s] = en[mem.val.expr.to_s].to_i * -1 else en[mem.name.to_s] = mem.val.val.to_i end end } # -- STRUCTURE (todo: not yet implemented) elsif C::Struct === e.type then @types << 'struct ' + e.type.name @structs << [e.type.name, []] @synon[e.type.to_s] = @all_synon[e.type.to_s] # st = (@structs[e.type.name] = []) # e.type.members.each { |mem| # #todo: nested structures, initializers, etc # st << [mem.declarators[0].name, mem.declarators[0].type.to_s] #} # -- FUNCTION DECLARATION elsif e.declarators.length > 0 && C::Function === e.declarators[0].type functype = e.declarators[0].indirect_type decl = {} decl[:name] = e.declarators[0].name decl[:varargs] = e.declarators[0].type.var_args? decl[:full] = e.type.to_s + FunctionPtr.pointers(functype.type) decl[:full] << ' ' << decl[:name] << ' (' functype.params.each_with_index { |p,i| if (C::CustomType === p.type && p.type.name == 'va_list') then decl[:varargs] = true next # we leave this last va_args param out since it's a placeholder for the actual varargs params anyway end decl[:full] << ((i != 0) ? ', ' : '') if C::Pointer === p.type && (C::Int === p.type.direct_type || C::Float === p.type.direct_type) then decl[:full] << p.type.direct_type.to_s << ' ref' else fp = FunctionPtr.parse(p) if fp.nil? then decl[:full] << p.type.to_s else # one of the function parameters is a pointer to an anonymous function # we add it as void*, but then add also a prototype so it's possible to create # a callback for it (use Ewl_Funcname_ParN as callback name) # decl[:full] << 'void*' pname = decl[:name].split('_').map { |s| s.capitalize }.push('Arg'+i.to_s).join('_') fp.name = pname @protos[pname] = fp.to_s(false, false) end end } unless functype.params.nil? decl[:full] << ")" #breakpoint if functype.params.length > 0 unless functype.params.nil? @decls << decl else @synon[e.type.to_s] = @all_synon[e.type.to_s] end } puts to_s if @debug classgraph if @graph end class FunctionPtr attr_accessor :name, :ret, :params # Node shall be a C::Node # Note: if the function ptr returns a pointer to another function, we don't handle that case. # Either typedef it (saner) or make this routine more powerful. # def self.parse(node) f = FunctionPtr.new if C::Declaration === node then # the function pointer is in a typedef if node.typedef? then dec = node.declarators[0] return nil unless !dec.nil? && C::Pointer === dec.indirect_type && C::Function === dec.indirect_type.type f.name = dec.name ftype = dec.indirect_type.type f.ret = node.type.to_s # return type without any pointer qualifier f.ret << FunctionPtr.pointers(ftype.type) f.params = [] ftype.params.each { |par| p = FunctionPtr.parse(par) f.params << (( p.nil? ) ? par.type.to_s : p) } unless ftype.params.nil? return f end else return nil unless node.type.is_a?(C::Type) && C::Pointer === node.type && C::Function === node.type.type ftype = node.type.type f.params = [] ftype.params.each { |par| p = FunctionPtr.parse(par) f.params << (( p.nil? ) ? par.type.to_s : p) } f.name = ftype.name if ftype.respond_to?(:name) f.ret = ' ' while C::Pointer === ftype.type # type of function only stores pointer qualifiers in this case f.ret << '*' ftype = ftype.type end f.ret.insert(0, ftype.type.to_s) return f end return nil end def to_s(anon=true, as_ptr=true) s = @ret + ' ' s << '(*' if as_ptr s << @name unless anon s << ')' if as_ptr s << " (#{@params.map { |par| par.to_s }.join(', ')})" s end def self.pointers(ctype) p = '' while C::Pointer === ctype p << '*' ctype = ctype.type end return p end end def ignored?(name) @config[:ignore].each { |ig| if (Regexp === ig && name.match(ig)) || (String == ig && name == ig) puts "Ignoring symbol as requested: #{name}" return true end } return false end def create_dl_code declarations = '' @decls.each { |d| next if ignored?(d[:name]) declarations << "\textern '#{d[:full]}'" declarations << ", :varargs" if d[:varargs] declarations << "\n" } aliases = @config[:external_types].collect { |typ| talias = @config[:type_aliases][typ] talias = 'void' if talias.nil? "\ttypealias '#{typ}', '#{talias}'" } internal_aliases = "" @synon.each { |name, typ| internal_aliases << "\ttypealias '#{typ}', '#{name}'\n" } prototypes = '' @protos.each { |name, decl| prototypes << "\tprototype '#{decl}'\n" unless ignored?(name) talias = @config[:type_aliases][name] talias = 'void*' if talias.nil? aliases << "\ttypealias '#{name}', '#{talias}'" } aliases = aliases.join("\n") @config[:type_aliases].each { |typ, ali| next unless @config[:external_types].index(typ).nil? aliases << "\n\ttypealias '#{typ}', '#{ali}'" } enum_aliases = '' enumpool = "\tclass Constants\n" @enums.each { |enum, members| enumpool << "\t\t#----- #{enum.to_s}\n" enum_aliases << "\ttypealias '#{enum.to_s}', 'int'\n" members.each { |member, value| enumpool << "\t\t" << member.to_s.upcase << ' = ' << value.to_s << "\n" } enumpool << "\n" } enumpool << "\tend\n" struct_aliases = '' @structs.each { |struct| struct_aliases << "\ttypealias '#{struct}*', 'void*'\n" } base_class = nil classes = "" @config[:classes].each { |kl| classes << "\nclass #{kl[:name]} < " # this is the "base class" of the module and get special treatment. # there should be only one of these if kl[:child_of].nil? then if !base_class.nil? puts "[ERROR] There should be only one \"base class\" for each module. You have #{base_class} and #{kl[:name]} here, one should die." return exit else classes << "CClass" classes << "\n\tLIB_MODULE = Lib#{@config[:module_base_name]}" classes << "\n\textend CClass::CallbackMaker" base_class = kl[:name] end else classes << "#{kl[:child_of]}" end classes << "\n" classes << "\twraps_class '#{kl[:wraps]}'\n" unless kl[:wraps].nil? classes << "end\n" } if base_class.nil? puts "[ERROR] There should be at least one \"base class\" for each module. It will be used to catch all calls that aren't handled by any object in the hierarchy." return exit end handmade = "" if @handmade handmade << IO.read(@handmade) end libname = @config[:library_name] libname = "lib#{@config[:module_base_name].downcase}" if libname.nil? dl = <<-END_DL_CODE #------------------------------------------------------- # This piece of wrapper code was automatically generated #------------------------------------------------------- # Basic Ruby/DL module, which contains all the declarations of the wrapped functions, type aliases, callbacks # require File.dirname(File.expand_path(__FILE__)) + '/../dl_auto.rb' module Lib#{@config[:module_base_name]} extend DL::Importable dlload '#{libname}.so' BASE_NAME = '#{@config[:module_base_name].downcase}' # Type aliases (from external types) # #{aliases} # Type aliases (from enumerations) # #{enum_aliases} # Type aliases (from structures) # #{struct_aliases} # Type aliases (internal from the headers) # #{internal_aliases} # Function declarations # #{declarations} # Callback prototypes # #{prototypes} # Constants (from enumerations) # #{enumpool} end module #{@config[:module_base_name]} #------------------------------------------------------ # Class definitions. # #{classes} BASE_CLASS = #{base_class} extend CClass::Redirects # Hand made customizations # #{handmade} end END_DL_CODE return dl end def to_s o = '' o += title "#{@types.length} Types:" + @types.join(" | ") + "\n" o += title "#{@all_synon.length} Type aliases:" @all_synon.each { |syn,syn_to| o += "#{syn} => #{syn_to}\n" } o += "\n" o += title "#{@decls.length} Functions:" o += @decls.collect { |d| d[:full] }.join("\n") o += "\n" o += title "#{@protos.length} Prototypes:" o += @protos.values.join("\n") o += "\n" o += title "#{@enums.length} Enumerations:" @enums.each { |name,mem| o += name + "{\n" mem.each { |k,v| o += "\t\t#{k} => #{v}\n" } o += "}\n" } o += "\n" o += title "#{@structs.length} Structures:" @structs.each { |name, str| o += name + "\n" #~ o += name + '{ ' #~ str.each { |mem| #~ o += "\t\t#{mem[0]} => #{mem[1]}\n" #~ } #~ o += "}\n" } o += "\n" return o end def classgraph(k = nil, lev = 0) if k.nil? k = @config[:classes][0] puts title('Class tree') end puts "#{''.pad(lev-1,' ')} #{k[:name]} " + ((!k[:wraps].nil?) ? "(#{k[:wraps]})" : '') @config[:classes].each { |sub| classgraph(sub, lev+1) if (sub[:child_of] == k[:name]) } end def run(config_file, output_file) begin puts "Reading and parsing bindings configuration file" cfg = IO::read(config_file); cfg = eval(cfg); @config = cfg puts "Preprocessing the headers" run_preprocessor puts "Parsing the C code" parse_data puts "Generating Ruby bindings" dlcode = create_dl_code puts "Writing output file" open(output_file,'w') { |out| out.write(dlcode) } puts "Done !" rescue => e puts e.to_s puts e.backtrace return false end return true end end #------------------------------------------------------------------------ # You can run this file from the command line to generate your bindings, # after you have written a config file #------------------------------------------------------------------------ if __FILE__ == $0 def show_usage puts < err show_usage exit -1 end if ARGV.length < 2 show_usage exit -1 end exit -1 if !h2dl.run(ARGV[0], ARGV[1]) end