require 'rubygems' require 'nokogiri' module FFI module Generator @typedefs = {} TYPES = { 'char' => ':char', 'double' => ':double', 'float' => ':float', 'unsigned long' => ':ulong', 'unsigned char' => ':uchar', 'signed char' => ':char', 'unsigned char' => ':uchar', 'short' => ':short', 'signed short' => ':short', 'signed short int' => ':short', 'unsigned short' => ':ushort', 'unsigned short int' => ':ushort', 'int' => ':int', 'signed int' => ':int', 'unsigned int' => ':uint', 'long' => ':long', 'long int' => ':long', 'signed long' => ':long', 'signed long int' => ':long', 'unsigned long' => ':ulong', 'unsigned long int' => ':ulong', 'long unsigned int' => ':ulong', 'long long' => ':long_long', 'long long int' => ':long_long', 'signed long long' => ':long_long', 'signed long long int' => ':long_long', 'unsigned long long' => ':ulong_long', 'unsigned long long int' => ':ulong_long', 'void' => ':void' } class << self attr_reader :typedefs def add_type(ctype, rtype) @typedefs[ctype] = rtype end end class Node attr_reader :symname def initialize(node, indent = 0) @node = node @indent = ' ' * indent @symname = get_attr('name') end def get_attr(name) attr = (@node / "./attributelist/attribute[@name='#{name}']").first attr['value'] if attr end end class Type < Node def initialize(node, indent = 0) super @type = get_attr('type') @decl = get_attr('decl') @statement = @type.to_s + @decl.to_s end def to_s get_type end private def is_native? Generator::TYPES.has_key?(@type) end def is_pointer? @decl and @decl[/(\)\.p|^p)\./] end def is_enum? @type[/^enum/] end def is_array? @decl and @decl[/\w+\(\d+\)/] end def is_struct? @type[/^struct/] end def is_union? @type[/^union/] end def is_constant? @type[/^q\(const\)/] end def native if is_native? @type = Generator::TYPES[@type] get_type end end def constant if is_constant? @type = @type.scan(/^q\(const\)\.(.+)/).flatten[0] get_type end end def pointer if is_pointer? if @type[/char/] @type = ':string' @decl.gsub!(/p./, '') get_type else return ':pointer' end end end def array if is_array? num = @decl.scan(/\w+\((\d+)\)/).flatten[0] @decl.gsub!(/\w+\(\d+\)/, '') "[#{get_type}, #{num}]" end end def struct if is_struct? @type = Structure.camelcase(@type.scan(/^struct\s(\w+)/).flatten[0]) get_type end end def union if is_union? @type = Union.camelcase(@type.scan(/^union\s(\w+)/).flatten[0]) get_type end end def enum if is_enum? @type = Generator::TYPES['int'] get_type end end def typedef if Generator.typedefs.has_key?(@type) @type = Generator.typedefs[@type] get_type end end def get_type constant || pointer || enum || typedef || native || struct || union || array || "#{@type}" end end class Typedef < Type attr_reader :symname, :type def initialize(node, indent = 0) super @symname = get_attr('name') @type = is_pointer? ? ':pointer' : get_attr('type') end end class Constant < Node def initialize(node, indent = 0) super @name, @value = get_attr('sym_name'), get_attr('value') end def to_s @indent + "#{@name} = #{@value}" end end class Enum < Node def initialize(node, indent = 0) super eval_items end def to_s @items.sort { |i1, i2| i1[1] <=> i2[1] }.inject("") do |result, item| result << assignment_str(item[0], item[1]) << "\n" end end private def assignment_str(name, value) @indent + "#{name} = #{value}" end def eval_expr(expr) if expr.include?('+') (@items[expr[/\w+/]].to_i + 1).to_s else 0.to_s end end def eval_items @items = {} get_items.each do |i| node = Node.new(i) @items[node.get_attr('name')] = node.get_attr('enumvalueex') ? eval_expr(node.get_attr('enumvalueex')) : node.get_attr('enumvalue') end @items end def get_items @node / './enumitem' end end class Structure < Node def self.camelcase(name) name.gsub(/^\w|\_\w/).each {|c| c.upcase }.delete('_') end def initialize(node, indent = 0) super @name = self.class.camelcase(@symname) end def to_s fields_str = fields.inject("") do |str, f| str << @indent + ' ' * 9 << f.join(', ') << ",\n" end code = klass_string + @indent + " layout(\n" + fields_str.chomp.chomp(',') + "\n" + @indent + " )\n" + @indent + "end\n" end private def klass_string @indent + "class #{@name} < FFI::Struct\n" end def fields (@node / 'cdecl').inject([]) do |array, field| array << [":#{Node.new(field).symname}", "#{Type.new(field)}"] end end end class Union < Structure private def klass_string @indent + "class #{@name} < FFI::Union\n" end end class Function < Node class Argument < Type def initialize(node, indent = 0) super @decl = @type end end def to_s params = get_params(@node).inject([]) do |array, node| array << Argument.new(node).to_s end.collect { |p| "#{p}" } @indent + "attach_function :#{@symname}, [ #{params.join(', ')} ], #{Type.new(@node).to_s}" end private def get_params(node) parmlist = node / './attributelist/parmlist/parm' end end class Parser @indent = 2 class << self def get_verbatim(node) node.xpath("./attributelist/attribute[@name='code']").first['value'] end def is_insert_runtime?(node) section = node.xpath("./attributelist/attribute[@name='section']") section.first['value'] == 'runtime' if section.first end def is_constant?(node) node.name == 'constant' end def is_enum?(node) node.name == 'enum' end def is_function_decl?(node) node.name == 'cdecl' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'function' end def is_struct?(node) node.name == 'class' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'struct' end def is_union?(node) node.name == 'class' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'union' end def is_typedef?(node) node.name == 'cdecl' and (node / "./attributelist/attribute[@name='kind']").first['value'] == 'typedef' end def generate(node) result = "" node.traverse do |node| if is_constant?(node) result << Constant.new(node, @indent).to_s << "\n" elsif is_typedef?(node) typedef = Typedef.new(node) Generator.add_type(typedef.symname, typedef.type) elsif is_enum?(node) e = Enum.new(node, @indent) Generator.add_type(e.symname, Generator::TYPES['int']) result << e.to_s << "\n" elsif is_struct?(node) s = Structure.new(node, @indent) Generator.add_type(s.symname, "struct #{s.symname}") result << s.to_s elsif is_union?(node) s = Union.new(node, @indent) Generator.add_type(s.symname, "union #{s.symname}") result << s.to_s elsif is_function_decl?(node) result << Function.new(node, @indent).to_s << "\n" elsif node.name == 'insert' and not is_insert_runtime?(node) result << get_verbatim(node) end end result end end end end end