#!/usr/bin/ruby =begin Authors: Wei Li Copyrights: Copyright (C) 2007 by Wei Li License: GPL This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =end require 'rexml/document' require 'set' require 'pathname' require 'optparse' =begin TODO: * 调用约定 * 产生多个D模块 * 对齐 * D2 =end class SourceFile attr_accessor :name def initialize(name) @name = name end end class Parser XML_ROOT_ELEMENT = "GCC_XML" #C中不存在的D关键字 @@DKeywords = Set.new [ "byte", "ubyte", "ushort", "uint", "ulong", "wchar", "dchar", "cent", "ucent", "real", "ifloat", "idouble", "ireal", "cfloat", "cdouble", "creal", "ref", "in", "out", "inout", "throw", "try", "finally", "catch", "lzay", "macro", "alias", "private", "protected", "public", "package", "invariant", "scope", "final", "template", "override", "import", "mixin", "is", "with", "class", "this", "new", "delete", "align", "unittest", "version", "debug", "assert", "__traits", "pragma", "super", "synchornized", "typeof", "typeid", "null", "module", "interface", "export", "delegate", "deprecated", "cast", "abstract", "body", "bool", "foreach", "foreach_reverse", "function", "true", "false" # 还有吗? ] attr_reader :main_file def initialize(xml_file, writer) @id_count = 0 @writer = writer @xmlroot = REXML::Document.new(File.open(xml_file)).elements[XML_ROOT_ELEMENT] builitin_ele = @xmlroot.elements["File[contains(@name, 'gccxml_builtins.h')]"].attributes["id"] #去掉 GCCXML 内置的东西 @xmlroot.elements.delete_all "*[@file='#{builitin_ele}']" @elements_cache = Hash.new @xmlroot.each_element { |node| @elements_cache[node.attributes["id"]] = node } f0_name = @xmlroot.elements["File[@id='f0']"].attributes["name"] @main_file = SourceFile.new(f0_name) end def generate_id @id_count += 1 return "__HTD_gen_" + @id_count.to_s end def get_element_by_id(id) @elements_cache[id] end def get_name(ele) name = ele.attributes["name"] if not name then return nil end result = filter_keyword name #(mangled ? demangled : name) return result end def generate_linkage(func_ele) "extern(System)" end def generate_fundamental_type(type_ele) size = type_ele.attributes["size"].to_i case type_ele.attributes["name"] when "short int" : "short" when "short unsigned int" : "ushort" when "int" : "int" when "long int" : "int" when "unsigned int" : "uint" when "long unsigned int" : "int" when "long long int" : "long" when "long long unsigned int" : "ulong" when "float" : "float" when "double" : "double" when "char" : "char" when "unsigned char" : "ubyte" when "signed char" : "byte" when "void" : "void" when "wchar_t" : if size > 16 then "dchar" else "wchar" end when "signed wchar_t" : if size > 16 then "int" else "short" end when "unsigned wchar_t" : if size > 16 then "dchar" else "wchar" end end end def generate_array(e) length = e.attributes["max"].to_i + 1 - e.attributes["min"].to_i dcode = "[#{length.to_s}]" end def generate_type_chain(ele, chain) #生成类型链 type = ele.attributes["type"] chain << ele if type then generate_type_chain(get_element_by_id(type), chain) end end def generate_type(type_id, kind) #kind 有这几种: :Variable, :Parameter, :Return, :Field, :Typedef ele = get_element_by_id type_id chain = [] generate_type_chain ele, chain dtype = [] is_pointer = false is_array = false is_typedef = false is_const = false chain.reverse_each do |e| case e.name when "FundamentalType" then dtype << generate_fundamental_type(e) when "Enumeration" then dtype << get_name(e) when "FunctionType" then dtype << generate_function_type(e) when "Struct" then dtype << get_name(e) #嵌套的struct/union允许无名字段,但是D不允许 when "Union" then dtype << get_name(e) when "PointerType" then dtype << "*"; is_pointer = true when "ArrayType" then dtype << generate_array(e); is_array = true #这步忽略 Typedef and const when "Typedef" then is_typedef = true # yeah,我们忽略所有 Typedef when "CvQualifiedType" then is_const = true #什么也不干 dtype << " const " else error "Unknown type '#{e.name}'" end end def is_atomic_type(aele) return aele.name == "FundamentalType" || aele.name == "Enumeration" ? true : false end #处理 const, D1 对 constness 的支持有限, 只支持在 :Variable 中使用 if kind == :Variable then #情况1: const atomic_type 形式 if chain.length == 2 && is_atomic_type(chain.last) && chain[chain.length - 2].name == "CvQualifiedType" then dtype.insert 0, "const" end #情况2: const typedef atomic_type 形式 if chain.length == 3 && is_atomic_type(chain.last) && chain[chain.length - 3].name == "CvQualifiedType" then dtype.insert 0, "const" end end return dtype.join(" ") end def generate_parameters(func_ele) params = [] func_ele.elements.each("Argument") do |arg| type = generate_type(arg.attributes["type"], :Parameter) name = get_name arg params << if name then "#{type} #{name}" else "#{type}" end end if params.length == 1 then return params[0] else return params.join(", ") end end def emit_function(e) linkage = generate_linkage(e) return_type = generate_type(e.attributes["returns"], :Return) function_name = get_name e parameters = generate_parameters(e) return "#{linkage} #{return_type} #{function_name}(#{parameters});\n" end def generate_function_type(e) return_type = generate_type(e.attributes["returns"], :Return) parameters = generate_parameters(e) return "#{return_type} function(#{parameters})" end def generate_members(members) #处理无名成员,为它们加上名字 members.each do |mid| member_ele = get_element_by_id(mid) mname = member_ele.attributes["name"] if !mname or mname == "" then member_ele.attributes["name"] = generate_id end end #处理成员 dcode = "" members.each do |mid| member_ele = get_element_by_id(mid) dcode << case member_ele.name when "Field" then generate_field member_ele #由于已经在外部定义了 struct/union,在此就忽略内部的 struct/union 乐 #when "Struct" then generate_aggregation_body member_ele #when "Union" then generate_aggregation_body member_ele else "" end end return dcode end def emit_aggregation(aggr_ele) generate_aggregation_body(aggr_ele) end def generate_aggregation_body(aggr_ele) aggr_name = get_name aggr_ele type_name = case aggr_ele.name when "Struct" : "struct" when "Union" : "union" else error "Invalid aggreation element '#{aggr_ele.name}'" end dcode = "\n#{type_name} #{aggr_name}\n" dcode << "{\n" members = aggr_ele.attributes["members"] if members and members.length > 0 then dcode << generate_members(members.split(" ")) end dcode << "}\n\n" return dcode end def emit_typedef(typedef_ele) typedef_name = get_name(typedef_ele) type = generate_type(typedef_ele.attributes["type"], :Typedef) dcode = "alias #{type} #{typedef_name};\n" return dcode end def generate_var_init(ele) init = ele.attributes["init"] if !init then return nil end init.sub!(/\{/, "[") init.sub!(/\}/, "]") chain = [] generate_type_chain ele, chain #GCCXML 生成的多维数组初始化属性是错误地,所以我们简单地忽略它 if chain.length > 2 then 0.upto chain.length - 2 do |i| if chain[i].name == "ArrayType" and chain[i + 1].name == "ArrayType" then return nil end end end #由于D中的有名Enum包含域,需要特殊处理 if chain.last.name == "Enumeration" and not chain.last.attributes["name"] =~ /\._\d+/ then return "#{get_name chain.last}.#{init}" end return init end def emit_variable(var_ele) var_name = get_name var_ele linkage = generate_linkage var_ele type = generate_type(var_ele.attributes["type"], :Variable) init = generate_var_init var_ele dcode = "#{type} #{var_name}" if var_ele.attributes["extern"] then dcode = "extern #{dcode}" end if init then dcode = "#{dcode} = #{init}" end dcode = "#{linkage} #{dcode};\n" end def generate_field(ele) name = get_name ele if not name then name = "" end type = generate_type(ele.attributes["type"], :Field) return "#{type} #{name};\n" end def emit_enum(enum_ele) name = get_name enum_ele if name =~ /\._\d+/ then name = "" end dcode = "enum " << name << " : int \n" dcode << "{\n" members = [] enum_ele.elements.each("EnumValue") do |ev| evname = get_name ev init = ev.attributes["init"] members << "\t#{evname} = #{init}" end if members.length <= 1 dcode << members[0] else dcode << members.join(",\n") end dcode << "\n}\n" end #防止C代码里的符号与D关键字冲突 def filter_keyword(symbol) if @@DKeywords.include?(symbol) then symbol + "_" else symbol end end def parse_defines(filename) #解析 #define f = File.new(filename, "r") #C/C++ 中该死的 wchar_t 长度是不确定的,VC中是16位,GCC是32位,因此: #看看我们能否找到 wchar_t 类型 wchar_t = "wchar" #默认是 wchar wce = @xmlroot.elements["FundamentalType[@name='wchar_t']"] if wce and wce.attributes["size"].to_i > 16 then wchar_t = "dchar" end while line = f.gets #C中int类型可能由L结尾 if line =~ /^\s*#define\s+(\w+)\s+\(?([-\+\.\w]+)L?\)?\s*$/ #数值型 @writer << "const auto #{filter_keyword($1)} = #{$2};\n" elsif line =~ /^\s*#define\s+(\w+)\s+(\'.*\')\s*$/ then #ANSI 字符 @writer << "const char #{filter_keyword($1)} = #{$2};\n" elsif line =~ /^\s*#define\s+(\w+)\s+(L\'.*\')\s*$/ then #wchar_t 字符 @writer << "const #{wchar_t} #{filter_keyword($1)} = #{$2};\n" elsif line =~ /^\s*#define\s+(\w+)\s+(\".*\")\s*$/ then #ANSI 字符串 @writer << "const char* #{filter_keyword($1)} = #{$2};\n" elsif line =~ /^\s*#define\s+(\w+)\s+(L\".*\")\s*$/ then #wchar_t 字符串 @writer << "const #{wchar_t}* #{filter_keyword($1)} = #{$2};\n" end end end def parse_decl @xmlroot.each_element("Enumeration[@file='f0']") { |node| @writer << emit_enum(node) } @xmlroot.each_element("Struct[@file='f0']") { |node| @writer << emit_aggregation(node) } @xmlroot.each_element("Union[@file='f0']") { |node| @writer << emit_aggregation(node) } @xmlroot.each_element("Typedef[@file='f0']") { |node| @writer << emit_typedef(node) } @xmlroot.each_element("Function[@file='f0']") { |node| @writer << emit_function(node) } @xmlroot.each_element("Variable[@file='f0']") { |node| @writer << emit_variable(node) } #@xmlroot.each_element("File") { |node| puts node } end def parse parse_defines main_file.name parse_decl end end #end of class Parser def show_help show_logo puts "Usage:" puts "ruby htd.rb <.h file>" puts "\nOptions:" puts " -o DFILE\t\tname D header file to 'DFILE'" puts " -g \t\t to GCCXML" puts " -H/-?/--Help\t\tshow this help" puts exit 2 end def show_logo puts "HTD 0.004 - A Binder for The D Programming Language" puts "Copyright (C) 2007 by Wei Li " puts "This is free software; see the source for copying conditions. There is NO" puts "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." end def error(msg) puts "Error:" << msg exit 1 end d_filename = "generated.d" gccxml_options = [] options = OptionParser.new options.on("-?", "-H", "--Help") { |val| show_help } options.on("-o [DFILE]", String) { |val| d_filename = val } options.on("-g STRING", String) { |val| gccxml_options << val } args = options.parse(*ARGV) h_filename = "" if args.length > 1 then error "too many arguments" end h_filename = args[0] XmlFile = "output.xml" DVersion2 = false Cmd = "gccxml #{gccxml_options.join(" ")} #{h_filename} -fxml=#{XmlFile}" gccxml_result = system Cmd unless gccxml_result then error "Failed to execute gccxml" end writer = File.new(d_filename, "w") gen = Parser.new(XmlFile, writer) gen.parse #解析并生成 D 文件