# HG changeset patch # User Jacob Carlborg # Date 1233418964 -3600 # Node ID c7db221de6e8c4f34b1d93e4fb0145d9d99afa79 First upload of dgen.rb and dstepgen.rb diff -r 000000000000 -r c7db221de6e8 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Sat Jan 31 17:22:44 2009 +0100 @@ -0,0 +1,11 @@ +syntax: glob +*.DS_Store +*.o +dsss_objs +dsss.last +*.orig +dsss_imports +*.sh +*.a +*.rf +*.dylib \ No newline at end of file diff -r 000000000000 -r c7db221de6e8 sciprts/dgen.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciprts/dgen.rb Sat Jan 31 17:22:44 2009 +0100 @@ -0,0 +1,1349 @@ +#!/usr/bin/env ruby + +## +# Copyright:: Copyright (c) 2009 Jacob Carlborg. All rights reserved. +# Author:: Jacob Carlborg +# Version:: Initial created: 2009 +# License:: [revised BSD license]http://opensource.org/licenses/bsd-license.php or +# [Academic Free License v3.0]http://opensource.org/licenses/afl-3.0.php +# + +require "rubygems" +gem "xml-simple" +require "xmlsimple" +require "optparse" + +# Extensions to String +class String + + # Passes each character to the supplied block + # + # === Example: + # "str".each_char { |c| puts c } + # # outputs below + # s + # t + # r + # + def each_char + if block_given? + scan(/./m) do |x| + yield x + end + else + scan(/./m) + end + end + + # Indents the string + # levels:: how many levels/tabs to indent + # + # === Example: + # "str".indent #=> str + # "str".indent(3) #=> str + # + def indent (levels = 1) + "\t" * levels << self + end + + # Appends a semicolon and a newline character on the string + # semicolon:: should a semicolon be appended + # + # === Example: + # "str".nl + # "str".nl(false) + # # outputs below + # str; + # + # str + # + # + def nl (semicolon = true) + if semicolon + if self[-1, 1] == "{" || self[-1, 1] == "}" + self << "\n" + else + self << ";\n" + end + else + self << "\n" + end + end + + # Returns the index of the given character or length of the string if not found + # char:: the character to look for + # start:: the index where to start the search + # + # === Example: + # "012345789".index_of("1") #=> 1 + # "0123 0123".index_of("3", 6) #=> 8 + # + def index_of (char, start = 0) + return self.length if start >= self.length + + i = 0 + + if start == 0 + self.each_char do |c| + return i if char == c + i += 1 + end + else + self[start + 1 .. -1].each_char do |c| + return i + start + 1 if char == c + i += 1 + end + end + + return self.length + end + + # Returns true if the string is an Objective-C struct + # + # === Example: + # "{?=ii}".struct? #=> true + # + def struct? + self =~ /\{/ + end +end + +# Extensions that adds support for member access syntax +class Hash + def type + result = self["type"] + return result unless result.nil? + self[:type] + end + + def type= (type) + self[:type] = type + end + + def id + result = self["id"] + return result unless result.nil? + self[:id] + end + + def id= (id) + self[:id] = id + end + + def methods + result = self["methods"] + return result unless result.nil? + self[:methods] + end + + def methods= (methods) + self[:methods] = methods + end + + def method + result = self["method"] + return result unless result.nil? + self[:method] + end + + def method= (method) + self[:method] = method + end + + def method_missing (method, *args) + self.class.instance_eval do + define_method(method) do |*args| + if args.length > 0 + self[method[0 ... -1]] = args[0] + self[eval(":#{method}"[0 ... -1])] = args[0] + else + result = self[method] + return result unless result.nil? + self[eval(":#{method}")] + end + end + end + + if (method = method.id2name) =~ /=/ + eval("self.#{method} (args.length < 2 ? args[0] : args)") + else + eval("self.#{method}") + end + end +end + +# This Struct represents an Objective-C Framework +Framework = Struct.new(:name, :files) do + def initialize + self.files = [] + self.name = "" + end +end + +# This Struct represents a C/Objective-C header +HeaderFile = Struct.new(:name, :framework, :cftypes, :constants, :d_constants, :d_constants_static_this, :defines, + :enums, :functions, :function_wrappers, :imports, :path, :structs, :typedefs) do + def initialize + self.name = "" + self.cftypes = [] + self.constants = [] + self.defines = [] + self.enums = [] + self.framework = "" + self.functions = [] + self.function_wrappers = [] + self.imports = [] + self.path = "" + self.structs = [] + self.typedefs = [] + end +end + +# Performs the conversion of an xml metadata file to the D programming language +class ObjcToD + FIRST_YEAR = "2009" + VERSION = 1.0 + + attr_accessor :out_dir, :files + + # Creates a new instance of the ObjcToD class + def initialize + @classes = {} + @copyright = nil + @d_constants = [] + @d_constants_static_this = [] + @files = [] + @frameworks = [] + @function_wrappers = [] + @headers = [] + @package = "dstep" + end + + # Generates the D code from the xml metadata + def generate_code + @files.each do |dstep_file| + xml = XmlSimple.xml_in(dstep_file) + + unless xml.framework.nil? + frameworks = xml.framework + + frameworks.each do |frame| + framework = Framework.new + framework.name = frame.name + + frame.file.each do |file| + header = HeaderFile.new + header.name = file.name + header.constants = constants(file.constant) unless file.constant.nil? + header.d_constants = d_constants unless file.constant.nil? + header.d_constants_static_this = d_constants_static_this unless file.constant.nil? + header.enums = enums(file.enum) unless file.enum.nil? + header.functions = functions(file.function) unless file.function.nil? + header.function_wrappers = function_wrappers unless file.function.nil? + header.imports = imports(file.import, file.name, framework.name) unless file.import.nil? + header.structs = structs(file.struct) unless file.struct.nil? + + header.typedefs = typedefs(file.typedef) unless file.typedef.nil? + + framework.files << header + end + + @frameworks << framework + end + end + + unless xml.file.nil? + files = xml.file + + file.each do |file| + header = HeaderFile.new + header.name = file.name + header.constants = constants(file.constant) unless file.constant.nil? + header.d_constants = d_constants unless file.constant.nil? + header.d_constants_static_this = d_constants_static_this unless file.constant.nil? + header.enums = enums(file.enum) unless file.enum.nil? + header.functions = functions(file.function) unless file.function.nil? + header.function_wrappers = function_wrappers unless file.function.nil? + header.imports = imports(file.import, file.name) unless file.import.nil? + header.structs = structs(file.struct) unless file.struct.nil? + header.typedefs = typedefs(file.typedef) unless file.typedef.nil? + + @headers << header + end + end + + unless xml["class"].nil? + classes(xml["class"]) + end + end + end + + # Outputs the generate D code + def output_code + @frameworks.each do |framework| + framework_path = framework_path = "#{@out_dir}/#{@package}/#{framework.name}" + + FileUtils.mkdir_p(framework_path) unless File.exist?(framework_path) + + framework.files.each do |header| + file_path = "#{framework_path}/#{header.name}.d" + + File.open(file_path, "w") do |file| + file << copyright + file << "module #{@package}.#{framework.name}.#{header.name};" + file << header.imports.nl(false) + file << header.defines + file << header.typedefs + file << header.cftypes + file << header.constants + file << header.d_constants + file << header.enums + file << header.structs + + unless header.d_constants_static_this.nil? + file << "static this ()".nl(false) + file << "{".nl(false) + file << header.d_constants_static_this + file << "}".nl(false).nl(false) + end + + classes = get_classes(header.name) + + classes.each do |clazz, value| + file << value.code.nl(false) + @classes.delete(clazz) + end + + file << header.functions + file << header.function_wrappers + end + end + end + + package_path = "#{@out_dir}/#{@package}" + FileUtils.mkdir_p(package_path) unless File.exist?(package_path) + + @headers.each do |header| + header_path = "#{package_path}/#{header.name}.d" + + File.open(header_path, "w") do |file| + file << copyright + file << "module #{@package}.#{framework.name}.#{header.name};" + file << header.imports.nl(false) + file << header.defines + file << header.typedefs + file << header.cftypes + file << header.constants + file << header.d_constants + file << header.enums + file << header.structs + + unless header.d_constants_static_this.nil? + file << "static this ()".nl(false) + file << "{".nl(false) + file << header.d_constants_static_this + file << "}".nl(false).nl(false) + end + + classes = get_classes(header.name) + + classes.each do |clazz, value| + file << value.code.nl(false) + @classes.delete(clazz) + end + + file << header.functions + file << header.functions_wrappers + end + end + + @classes.each do |clazz, value| + class_path = "#{package_path}/#{clazz}.d" + + File.open(class_path, "w") do |file| + file << value.code unless value.nil? + end + end + end + + # Creates and returns the copyright header that is included in the top of every file + def copyright + return @copyright unless @copyright.nil? + + # Add an extra empty string in the begining because array indices begin with zero and months don't + months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + initialt_year = "2009" + + str = StringIO.new + date = Date.today + current_year = date.year.to_s + year = initialt_year + initial_created = "#{months[date.month]} #{date.day}, #{initialt_year}" + + year << "-" << current_year unless initialt_year == current_year + + str << "/**\n" + str << " * Copyright: Copyright (c) #{year} Jacob Carlborg. All rights reserved.\n" + str << " * Authors: Jacob Carlborg\n" + str << " * Version: Initial created: #{initial_created} \n" + str << " * License: $(LINK2 http://opensource.org/licenses/bsd-license.php, revised BSD license) or
\n" + str << " * $(LINK2 http://opensource.org/licenses/afl-3.0.php, Academic Free License v3.0)\n" + str << " */\n" + + @copyright = str.string + end + + # Gets name of the framework that the given class belongs to + def get_framework (class_name) + @frameworks.each do |framework| + framework.files.each do |file| + return framework.name if file.name == class_name + end + end + + return [] + end + + # Gets the classes that belongs to the given file + def get_classes (name) + classes = @classes.find_all do |clazz, value| + value.file == name + end + + return classes + end + + # Generates the D code for the classes + def classes (classes) + classes.each do |clazz| + str = StringIO.new + + str << "class " + + if clazz == "" + str << clazz.name.nl(false) + else + str << clazz.name + str << " : " + str << clazz.parent.nl(false) + end + + str << "{".nl(false) + str << methods(clazz.method, clazz.name) + str << "}".nl(false) + + @classes[clazz.name] ||= {} + @classes[clazz.name].code = str.string + framework = get_framework(clazz.file) + @classes[clazz.name].framework = framework unless framework.nil? + @classes[clazz.name].file = clazz.file + end + end + + # Generates the D code for the constants/globals + def constants (constants) + str = StringIO.new + + str << "extern (C)".nl(false) + str << "{".nl(false) + str << "extern".indent.nl(false) + str << "{".indent.nl(false) + + constants.each do |constant| + type = get_type(constant.type, constant["type64"], constant["declaredType"]) + const = constant["const"] == "true" + + if constant.type == "@" + @d_constants << { :name => constant.name.dup, :type => constant.declaredType.gsub("*", ""), :const => const } + + if const + str << "const id".indent(2) + else + str << "id".indent(2) + end + + str << " " + str << constant["name"] + str << "_".nl + else + if const + str << "const ".indent(2) + str << type + else + str << "id".indent(2) + end + + str << " " + str << constant["name"].nl + end + end + + str << "}".indent.nl(false) + str << "}".nl(false).nl(false) + + str.string + end + + # Generates the D code for the constants that D can't handle directly, like classes + def d_constants + str = StringIO.new + + @d_constants.each do |constant| + # Deep copy constant + c = constant.dup + c.name = c.name.dup + c.type = c.type.dup + + str << "const " if constant.const + str << constant.type + str << " " + str << constant.name.nl + @d_constants_static_this << c + end + + str << "\n" + @d_constants.clear + str.string + end + + # Generates the D code for the constants the has to be in a "static this" + def d_constants_static_this + str = StringIO.new + + @d_constants_static_this.each do |constant| + str << constant.name.indent + str << " = new " + str << constant.type + str << "(" + str << constant.name + str << "_)".nl + end + + @d_constants_static_this.clear + str.string + end + + # Generates the D code for the enums + def enums (enums) + str = StringIO.new + + enums.each do |enum| + str << "enum" + + if enum["name"] == "" + str << "\n{".nl + + enum.member.each_with_index do |member, i| + str << member["name"].indent + str << " = " + str << member.value + str << ",".nl(false) unless i == enum.member.length - 1 + end + else + str << enum["name"].nl + str << "{" + + enum["member"].each_with_index do |member, i| + str << member["name"].indent + str << " = " + str << member.value + str << ",".nl(false) unless i == enum.member.length - 1 + end + end + + str << "\n}".nl.nl(false) + end + + str.string + end + + # Generates the D code for the function/method args + def args (args, variadic, method = false) + return "" if args.nil? + + str = StringIO.new + if variadic + #p args + end + args.each do |arg| + + if method || arg.type != "@" + type = get_type(arg.type, arg.type64, arg.declaredType) + else + type = "id" + end + + str << type + str << " " + str << arg["name"] + str << ", " + end + + if variadic + else + end + + str << "..." if variadic + + return str.string if variadic + return str.string[0 ... -2] unless variadic + end + + # A helper function that generates the D code for the functions/methods + def build_function (function, method = false, static = false) + str = StringIO.new + variadic = function["variadic"] == "true" + return_type = "" + args = "" + original_type = function.returnValue[0].type + + if !method && original_type == "@" + return_type = "id" + else + return_type = get_type(original_type, function.returnValue[0].type64, function.returnValue[0].declaredType) + args(function.arg, variadic) unless function.arg.nil? + end + + str << "static " if static + str << return_type + str << " " + str << function["name"] + str << " (" + str << args(function["arg"], variadic, method) + str << ")".nl + + str.string + end + + # Generates the D code for the functions + def functions (functions) + str = StringIO.new + + str << "extern (C)".nl(false) + str << "{".nl(false) + + functions.each do |function| + wrapper_needed = false + original_type = function["returnValue"][0].type + + if original_type == "@" + @function_wrappers << function + wrapper_needed = true + else + function["arg"].each do |arg| + if (arg.type || arg["type64"]) == "@" + @function_wrappers << function + wrapper_needed = true + end + end unless function["arg"].nil? + end + + return_type = get_type(function["returnValue"][0].type, function["returnValue"][0]["type64"], function["returnValue"][0]["declaredType"]) + variadic = function["variadic"] == "true" + + str << functions_helper(function, wrapper_needed) + end + + str << "}".nl(false).nl(false) + str.string + end + + # A helper function that generates the D code for the functions + def functions_helper (function, wrapper_needed) + str = StringIO.new + + if wrapper_needed + declaration = build_function(function) + index = declaration.index_of(" ") + index = declaration.index_of(" ", index) + str << (declaration[0 ... index] + "_" + declaration[index .. -1]).indent + else + str << build_function(function).indent + end + + str.string + end + + # Generates the D code for the functions that D can't handle without wrappers, like functions that takes + # objects as arguments or returns an object + def function_wrappers + str = StringIO.new + + @function_wrappers.each do |function| + original_type = function["returnValue"][0].type + variadic = function["variadic"] == "true" + args = StringIO.new + out_args = [] + return_type = get_type(original_type, function.returnValue[0].type64, function.returnValue[0].declaredType) + + function["arg"].each do |arg| + args << arg["name"] + + if arg.type == "@" + args << " !is null ? " + args << arg["name"] + args << ".id : null" + elsif arg.type == "^@" + type = get_type(arg.type, arg["type64"], arg["declaredType"]) + out_args << { :type => type[4 .. -1], :name => arg["name"] } + + args << " &" + args << arg["name"] + args << "_.id" + end + + args << ", " + end unless function["arg"].nil? + + str << build_function(function, true)[0 ... -2].nl(false) + str << "{".nl(false) + + out_args.each do |arg| + str << arg.type.indent + str << " " + str << arg.name + str << "_ = new " + str << arg.type + str << "(false, false)".nl + end + + str << "\n" if out_args.length > 0 + + if original_type == "@" + str << "id result = ".indent + str << function["name"] + elsif original_type != "v" + if out_args.length > 0 + str << return_type.indent + str << " result = " + else + str << "return ".indent + end + end + + if original_type == "v" + str << function["name"].indent + elsif original_type != "@" + str << "cast(" + str << return_type + str << ") " + str << function["name"] + end + + str << "_" + str << "(" + str << args.string[0 ... -2] unless function["arg"].nil? + str << ")".nl + str << "\n" if out_args.length > 0 + + out_args.each do |arg| + str << arg.name.indent(2) + str << " = " + str << arg.name + str << "_".nl + end + + if out_args.length > 0 + str << "\n" + str << "return result".indent unless original_type == "v" + end + + if original_type == "@" + str << "return result !is null ? new ".indent + str << return_type + str << "(result)".nl + elsif original_type != "v" && out_args.length > 0 + str << "".nl + end + + str << "}".nl(false).nl(false) + end + + @function_wrappers.clear + str.string[0 ... -2] + end + + # Generates the D code for the imports + def imports (imports, filename, framework_name = nil) + str = StringIO.new + + imports.each do |import| + str << "import #{@package}." + str << import.gsub("/", ".").gsub("Frameworks", "").gsub(".framework", "").gsub("Headers", "").gsub(/\.{2,}/, ".").nl + end + + str << "\n\n" + str = str.string.sort.to_s + + return "\n\npublic:" << str if filename == framework_name + return str + end + + # Generates the D code for the methods + def methods (methods, class_name) + str = StringIO.new + + str << "this (bool init = true, bool alloc = true)".indent.nl(false) + str << "{".indent.nl(false) + str << "if (alloc && init)".indent(2).nl(false) + str << "{".indent(2).nl(false) + str << "id result = objc_msgSend(class_#{class_name}, sel_alloc)".indent(3).nl + str << "id result2".indent(3).nl.nl(false) + str << "if (result)".indent(3).nl(false) + str << "result2 = objc_msgSend(result, sel_init)".indent(4).nl.nl(false) + str << "if (result2)".indent(3).nl(false) + str << "this.id = result2".indent(4).nl.nl(false) + str << "else".indent(3).nl(false) + str << "this.id = result".indent(4).nl + str << "}".indent(2).nl(false).nl(false) + str << "else if (alloc)".indent(2).nl(false) + str << "this.id = objc_msgSend(this.id, sel_alloc)".indent(3).nl + str << "}".indent.nl(false).nl(false) + + str << "this (id object)".indent.nl(false) + str << "{".indent.nl(false) + str << "this.id = object".indent(2).nl + str << "}".indent.nl(false).nl(false) + + str << "this (id object)".indent.nl(false) + str << "{".indent.nl(false) + str << "this.id = object.id".indent(2).nl + str << "}".indent.nl(false).nl(false) + + str << "static #{class_name} alloc ()".indent.nl(false) + str << "{".indent.nl(false) + str << "id result = objc_msgSend(class_#{class_name}, sel_alloc)".indent(2).nl + str << "return result !is null ? new #{class_name}(result) : null".indent(2).nl + str << "}".indent.nl(false).nl(false) + + str << "#{class_name} init ()".indent.nl(false) + str << "{".indent.nl(false) + str << "id result = objc_msgSend(this.id, sel_init)".indent(2).nl + str << "return result is this.id ? this : (result !is null ? new #{class_name}(result) : null)".indent(2).nl + str << "}".indent.nl(false).nl(false) + + methods.each do |method| + next if method.selector == ("alloc" || "init") && method.static == "true" + + return_type = get_type(method["returnValue"][0].type, method["returnValue"][0]["type64"], method["returnValue"][0]["declaredType"]) + variadic = method["variadic"] == "true" + static = method["static"] == "true" + this = "this.id" unless static + this = "class_#{class_name}" if static + args = "" + out_args = [] + original_type = method.returnValue[0].type + + if method.selector[0 ... 4] == "init" && return_type == "id" + return_type = class_name + method.returnValue[0].declaredType = class_name + end + + method["arg"].each do |arg| + args << " &" if arg.type == "^@" + args << arg["name"] + + if arg.type == "@" + args << " !is null ? " + args << arg["name"] + args << ".id : null" + elsif arg.type == "^@" + type = get_type(arg.type, arg.type, arg["declaredType"]) + out_args << { :type => type[4 .. -1], :name => arg["name"] } + args << "_.id" + end + + args << ", " + end unless method["arg"].nil? + + declaration = build_function(method, true, static)[0 ... -2].indent.nl(false) + index = declaration.index_of(" ") + + str << (declaration[0 .. index] + get_method_name(method["selector"]) + declaration[index .. -1]).gsub(/ +/, " ") + str << "{".indent.nl(false) + + out_args.each do |arg| + str << arg.type.indent(2) + str << " " + str << arg.name + str << "_ = new " + str << arg.type + str << "(false, false)".nl + end + + str << "\n" if out_args.length > 0 + + + if method["returnValue"][0].type == "@" + str << "id result = objc_msgSend(#{this}, sel_".indent(2) + str << transform_selector(method["selector"]) + str << ", " unless method.arg.nil? + str << args[0 ... -2] unless method.arg.nil? + str << ")".nl + str << "\n" if out_args.length > 0 + + out_args.each do |arg| + str << arg.name.indent(2) + str << " = " + str << arg.name + str << "_".nl + end + + str << "\n" if out_args.length > 0 + + if method["returnValue"][0]["declaredType"] == class_name + str << "return result is this.id ? this : (return result ".indent(2) + else + str << "return result ".indent(2) + end + + str << "!is null ? new " + str << return_type + str << "(result) : null" + + if method["returnValue"][0]["declaredType"] == class_name + str << ")".nl + else + str << "".nl + end + + str << "}".indent.nl(false).nl(false) + else + if original_type == "d" || original_type == "f" + + str << "version (X86)".indent(2).nl(false) + + if out_args.length > 0 + str << return_type.indent(3) + str << " result " + else + if original_type == "d" + str << "return ".indent(3) + else + str << "return cast(".indent(3) + str << return_type + str << ") " + end + end + + str << "objc_msgSend_fpret(#{this}, sel_" + str << transform_selector(method["selector"]) + str << ", " unless method.arg.nil? + + args = "" + + method["arg"].each do |arg| + args << arg["name"] + args << ", " + end unless method["arg"].nil? + + str << args[0 ... -2] if args.length > 0 + str << ")".nl.nl(false) + + str << "else".indent(2).nl(false) + + if out_args.length > 0 + str << return_type.indent(3) + str << " result " + else + if original_type == "d" + str << "return ".indent(3) + else + str << "return cast(".indent(3) + str << return_type + str << ") " + end + end + + str << "objc_msgSend(#{this}, sel_" + str << transform_selector(method["selector"]) + str << ", " unless method.arg.nil? + + args = "" + + method["arg"].each do |arg| + args << arg["name"] + args << ", " + end unless method["arg"].nil? + + str << args[0 ... -2] if args.length > 0 + str << ")".nl + str << "\n" if out_args.length > 0 + + out_args.each do |arg| + str << arg.name.indent(3) + str << " = " + str << arg.name + str << "_".nl + end + + if out_args.length > 0 + str << "\n" + str << "retrun result".indent(3).nl + end + + str << "\n" if out_args.length > 0 + str << "}".indent.nl(false).nl(false) + else + unless return_type == "void" + if out_args.length > 0 + str << return_type.indent(2) + str << " result " + else + str << "return cast(".indent(2) + end + + str << return_type + str << ") " + end + + str << "objc_msgSend(#{this}, sel_".indent(2) if return_type == "void" + str << "objc_msgSend(#{this}, sel_" unless return_type == "void" + str << transform_selector(method["selector"]) + str << ", " unless method.arg.nil? + + args = "" + + method["arg"].each do |arg| + args << arg["name"] + args << ", " + end unless method["arg"].nil? + + str << args[0 ... -2] if args.length > 0 + str << ")".nl + + str << "\n" if out_args.length > 0 + + out_args.each do |arg| + str << arg.name.indent(2) + str << " = " + str << arg.name + str << "_".nl + end + + if out_args.length > 0 + str << "\n" + str << "retrun result".indent(2).nl + end + + str << "}".indent.nl(false).nl(false) + end + end + end + + str.string[0 .. -2] + end + + # Generates the D code for the structs + def structs (structs) + str = StringIO.new + + structs.each do |struct| + str << "struct " + str << struct.name.nl(false) + str << "{".nl + + struct.member.each do |member| + type = get_type(member.type, member.type64, member.declaredType) + + str << type.indent + str << " " + str << member.name.nl + end unless struct.member.nil? + + str << "}".nl + str << "\n\n" + end + + str.string[0 .. -2] + end + + # Generates the D code for the typedefs + def typedefs (typedefs) + str = StringIO.new + + typedefs.each do |typedef| + + type = "" + struct = false + + if typedef.type.struct? + type = get_struct_type(typedef.type, typedef["type64"], typedef["declaredType"]) + struct = true + else + type = get_type(typedef.type, typedef.type64, typedef.declaredType) + end + + # Special case + type = "wchar" if typedef.name == "unichar" + + if struct + str << typedef["declaredType"].gsub("*", "").nl + str << "alias " + str << type[:name] + str << " " + str << typedef["name"].nl + else + str << "alias " + str << type + str << " " + str << typedef.name.nl + end + end + + str << "\n" + str.string + end + + # Adds specific D extensions to classes, like opIndex and opApply + def d_class_extensions (args) + + end + + # Checks if the declared type should be used instead of the type + # type:: the type to check + # + def check_declared_type (type) + case type + when "unichar"; return "wchar" + when "BOOL"; return "bool" + when "CGFloat"; return type + when "NSInteger"; return type + when "NSUInteger"; return type + + when "unichar*"; return "wchar*" + when "BOOL*"; return "bool*" + when "CGFloat*"; return type + when "NSInteger*"; return type + when "NSUInteger*"; return type + + when "unichar**"; return "wchar**" + when "BOOL**"; return "bool**" + when "CGFloat**"; return type + when "NSInteger**"; return type + when "NSUInteger**"; return type + else return nil; + end + end + + # Gets the method name from the supplied selector + # selector:: the selector to get the method name from + # + # === Example: + # get_method_name("initWithContentsOfURL:options:error:") #=> initWithContentsOfURL + # + def get_method_name (selector) + i = selector.index_of(":") + selector[0 ... i] + end + + # Gets the D type from the encoded C/Objective-C type + # type:: the type + # type64:: the type for 64bit targets + # declared_type:: the declared type + # + # === Example: + # get_type("I", "Q", "NSUInteger") #=> NSUInteger + # get_type("I", "I", "unsigned int") #=> uint + # + def get_type (type, type64, declared_type) + + t = check_declared_type(declared_type) + return t unless t.nil? + + unless type64 == "" || type64.nil? + return declared_type if type != type64 + end + + case type + when "c"; return "byte" + when "i"; return "int" + when "s"; return "short" + when "l"; return "int" + when "q"; return "long" + when "C"; return "ubyte" + when "I"; return "uint" + when "S"; return "ushort" + when "L"; return "uint" + when "Q"; return "ulong" + when "f"; return "float" + when "d"; return "double" + when "B"; return "bool" + when "v"; return "void" + when "*"; return "char*" + when '#'; return "Class" + when ":"; return "SEL" + when "@" + return declared_type.gsub(/\*+/, "") unless declared_type.nil? + raise "No declared type given" + else + case type[0, 1] + when "[" + str = "" + t = type[1 ... -1] + count = $1 if t =~ /(\d+)/ + str = get_type($', declared_type) + str << "[#{count}]" + + return str + when "(" + resolved_types = [] + types = $2 if type =~ /\((.+)=(.+)\)/ + + types.each_char do |t| + resolved_types << get_type(t, declared_type) + end + + return resolved_types + when "^" + if type == "^@" + return "out " << get_type(type[1 .. -1], type64[1 .. -1], declared_type).dup + elsif type.length > 2 && type[0 ... 2] == "^^" + return "out " << get_type(type[2 .. -1], type64[2 .. -1], declared_type).dup + elsif type == "^?" + tmp = cfp_to_dfp(type) + return tmp unless tmp.nil? + end + + if !type.nil? && !type64.nil? + return get_type(type[1 .. -1], type64[1 .. -1], declared_type).dup << "*" + elsif !type.nil? + return get_type(type[1 .. -1], type64, declared_type).dup << "*" + end + when "{" + return declared_type + end + end + + return declared_type + end + + # Gets the D type from the encoded C/Objective-C type when it's a struct + # type:: the type + # type64:: the type for 64bit targets + # declared_type:: the declared type + # + # === Example + # get_struct_type("{some=III}", "{some=III}", "struct some") + # # outputs below + # { :name => "some", :types => ["uint", "uint", "uint"] } + # + def get_struct_type (type, type64, declared_type) + + return { :name => declared_type } if declared_type[0 ... 2] == "NS" + + case type[0, 1] + when "{" + resolved_types = [] + + name = $1 if type =~ /\^{0,}\{(.{0,})=(.{0,})\}/ + types = $2 + + unless types.nil? + types.each_char do |t| + resolved_types << get_type(t, type64, declared_type) + end + end + + return { :name => name, :types => resolved_types } + when "^" + get_struct_type(type[1 .. -1], type64, declared_type) + hash = get_struct_type(type[1 .. -1], type64, declared_type) + hash[:name].dup << "*" + return hash + end + end + + # C Function Pointer to D Function Pointer + def cfp_to_dfp (fp) + reg = /(\w+)\s*\(\*(\w*)\)\s*\((.*)\)/ + + return nil if fp !~ reg + + return_type = $1 + name = $2 + arg_types = $3 + + return "#{return_type} function (#{arg_types})" + end + + # Is the supplied argument an "out" argument + # arg:: the arg + # + # === Example: + # out_arg?("out NSError") #=> true + # + def out_arg? (arg) + return arg[0 .. 4] == "out " + end + + # Transform the supplied selector to a valid D representation + # selector:: the selector to transform + # + # === Example: + # transform_selector("initWithContentsOfURL:options:error:") + # # outputs below + # initWithContentsOfURL_options_error_ + # + def transform_selector (selector) + selector.gsub(/:/, "_") + end +end + +# Prints the message to stderr, exits +def die (*msg) + $stderr.puts msg + exit 1 +end + +if __FILE__ == $0 + objc_to_d = ObjcToD.new + + OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] " + opts.separator "" + opts.separator "Options:" + + opts.on("-o", "--output DIRECTORY", "Place the output files in this directory'") do |opt| + die "The specified directory \"#{opt}\" does not exists" if File.exist?(opt) == false + die "Output directory cannot be specified more than once" if objc_to_d.out_dir + objc_to_d.out_dir = opt + end + + help_msg = "Use the `-h' flag or for help." + + opts.on("-h", "--help", "Show this message.") do + puts opts, help_msg + exit + end + + opts.on('-v', '--version', 'Show version.') do + puts ObjcToD::VERSION + exit + end + + opts.separator "" + + if ARGV.empty? + die opts.banner + else + begin + opts.parse!(ARGV) + + die "No output directory given" if objc_to_d.out_dir.nil? + + ARGV.each do |file| + objc_to_d.files << file + end + + objc_to_d.generate_code + objc_to_d.output_code + rescue => e + msg = e.message + msg = "Internal error" if msg.empty? + + die msg, opts.banner, help_msg + end + end + end +end \ No newline at end of file diff -r 000000000000 -r c7db221de6e8 sciprts/dstepgen.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sciprts/dstepgen.rb Sat Jan 31 17:22:44 2009 +0100 @@ -0,0 +1,1388 @@ +#!/usr/bin/env ruby + +# Copyright (c) 2008-2009, Jacob Carlborg. All rights reserved. +# Copyright (c) 2006-2007, Apple Inc. All rights reserved. +# Copyright (c) 2005-2006 FUJIMOTO Hisakuni +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of Apple Inc. ("Apple") nor the names of +# its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +require "rubygems" +gem "builder", "~> 2.0" +require "builder" +require "tmpdir" +require "optparse" +include Builder + +$KCODE = "UTF8" + +# Extensions that adds support for member access syntax +class Hash + def type + result = self["type"] + return result unless result.nil? + self[:type] + end + + def type= (type) + self[:type] = type + end + + def id + result = self["id"] + return result unless result.nil? + self[:id] + end + + def id= (id) + self[:id] = id + end + + def methods + result = self["methods"] + return result unless result.nil? + self[:methods] + end + + def methods= (methods) + self[:methods] = methods + end + + def method_missing (method, *args) + self.class.instance_eval do + define_method(method) do |*args| + if args.length > 0 + self[method[0 ... -1]] = args[0] + self[eval(":#{method}"[0 ... -1])] = args[0] + else + result = self[method] + return result unless result.nil? + self[eval(":#{method}")] + end + end + end + + if (method = method.id2name) =~ /=/ + eval("self.#{method} (args.length < 2 ? args[0] : args)") + else + eval("self.#{method}") + end + end +end + +# This Struct represents an Objective-C Framework +Framework = Struct.new(:name, :headers) do + def initialize + self.headers = [] + self.name = "" + end +end + +# This Struct represents a C/Objective-C header +HeaderFile = Struct.new(:name, :framework, :cftypes, :constants, :defines, :enums, :externs, :functions, + :imports, :inline_functions, :opaques, :path, :structs, :typedefs) do + def initialize + self.name = "" + self.cftypes = [] + self.constants = [] + self.defines = [] + self.enums = [] + self.externs = [] + self.framework = "" + self.functions = [] + self.imports = [] + self.inline_functions = [] + self.opaques = [] + self.path = "" + self.structs = [] + self.typedefs = [] + end +end + +# This class scans the headers +class HeaderScaner + CPP = ['/usr/bin/cpp-4.0', '/usr/bin/cpp-3.3', '/usr/bin/cpp3'].find { |x| File.exist?(x) } + raise "cpp not found" if CPP.nil? + CPPFLAGS = "-D__APPLE_CPP__ -include /usr/include/AvailabilityMacros.h" + CPPFLAGS << "-D__GNUC__" unless /\Acpp-4/.match(File.basename(CPP)) + + attr_accessor :frameworks, :headers, :do_64bit + + def initialize + @extern_name = 'extern' + @frameworks = [] + @file_content = nil + @headers = [] + @classes = {} + @informal_protocols = {} + @function_pointer_types = {} + @do_64bit = false + end + + def classes + @classes + end + + def protocols + @informal_protocols + end + + def cftypes (header) + re = /typedef\s+(const\s+)?(struct\s*\w+\s*\*\s*)([^\s]+Ref)\s*;/ + @cpp_result.scan(re).each do |m| + header.cftypes << { :name => m[2], :type => m[1], :const => m[0] =~ /const/ ? true : false} + end + end + + def constants (header) + tmp = header.externs.map do |i| + constant?(i, true) + end + + header.constants = tmp.flatten.compact + end + + def defines (header) + re = /#define\s+([^\s]+)\s+(\([^)]+\)|[^\s]+)\s*$/ + @file_content.scan(re).each do |m| + next unless !m[0].include?('(') && m[1] != '\\' + header.defines << { :name => m[0], :value => m[1] } + end + end + + def enums (header) + re = /\benum\b\s*(\w+\s+)?\{([^}]*)\}/ + @cpp_result.scan(re).each do |m| + enum = { :name => m[0], :members => [] } + + m[1].split(",").map do |i| + name, value = i.split("=", 2).map do |x| + x.strip + end + + enum[:members] << { :name => name, :value => value } unless name.empty? || name[0] == ?# + end + + header.enums << enum + end + end + + def function_pointer_types (header) + re = /typedef\s+([\w\s]+)\s*\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;/ + data = @cpp_result.scan(re) + re = /typedef\s+([\w\s]+)\s*\(([^)]+)\)\s*;/ + data |= @cpp_result.scan(re).map do |m| + ary = m[0].split(/(\w+)$/) + ary[1] << " *" + ary << m[1] + ary + end + + data.each do |m| + name = m[1] + args = m[2].split(",").map do |x| + if x.include?(" ") + ptr = x.sub!(/\[\]\s*$/, "") + x = x.sub(/\w+\s*$/, "").strip + ptr ? x + "*" : x + else + x.strip + end + end + + type = "#{m[0]}(*)(#{args.join(', ')})" + @function_pointer_types[name] = type + end + end + + def typedefs (header) + re = /^\s*typedef\s+(.+)\s+([\w\*]+)\s*;$/ + data = @cpp_result + data.scan(re).each do |m| + var = get_var_info(m[0] + " " + m[1]) + header.typedefs << get_var_info(m[0] + " " + m[1]) if var + end + end + + def externs (header) + re = /^\s*#{@extern_name}\s+\b(.*)\s*;.*$/ + header.externs = @cpp_result.scan(re).map do |m| + m[0].strip + end + end + + def imports (header) + tmp = [] + + @file_content.each do |line| + if line =~ /#(include|import) <(.+)\.h>/ + next if $2 == header.name + tmp << $2 + elsif line =~ /@class(\s+)(\w+)(,|;)/ + next if $2 == header.name + + if $3 == ";" + tmp << header.framework + "/" + $2 unless header.framework == "" + tmp << $2 if header.framework == "" + elsif + str = line[6 + $1.length ... -2] + str.gsub!(" ", "") + arr = str.split(",") + + arr.each do |s| + tmp << header.framework + "/" + s unless header.framework == "" + tmp << s if header.framework == "" + end + end + end + end + + header.imports = tmp.compact.uniq + end + + def informal_protocols (header) + self.methods(header) + end + + def methods (header) + interface_re = /^@(interface|protocol)\s+(\w+)\s*:?\s*(\w*)\s*(\([^)]+\))?/ + + end_re = /^@end/ + body_re = /^[-+]\s*(\([^)]+\))?\s*([^:\s;]+)/ + args_re = /\w+\s*:/ + prop_re = /^@property\s*(\([^)]+\))?\s*([^;]+);$/ + current_interface = current_category = nil + i = 0 + parent = nil + + @cpp_result.each_line do |line| + size = line.size + line.strip! + + if md = interface_re.match(line) + parent = nil + current_interface = md[1] == "protocol" ? "NSObject" : md[2] + current_category = md[4].delete("()").strip if md[4] + parent = md[3] unless md[3] == "" + + elsif end_re.match(line) + current_interface = current_category = nil + + elsif current_interface && md = prop_re.match(line) + # Parsing Objective-C 2.0 properties + if (a = md[2].split(/\s/)).length >= 2 && /^\w+$/.match(name = a[-1]) && (type = a[0 .. -2].join(" ")).index(",").nil? + getter, setter = name, "set#{name[0].chr.upcase + name[1 .. -1]}" + readonly = false + + if attributes = md[1] + if md = /getter\s*=\s*(\w+)/.match(attributes) + getter = md[1] + end + + if md = /setter\s*=\s*(\w+)/.match(attributes) + setter = md[1] + end + + readonly = true if attributes.index("readonly") + end + + typeinfo = VarInfo.new(type, "", "") + + @classes[current_interface] ||= {} + methods = (@classes[current_interface].methods ||= []) + methods << MethodInfo.new(typeinfo, getter, false, [], line) + + unless readonly + methods << MethodInfo.new(VarInfo.new("void", "", ""), setter + ":", false, [typeinfo], line) + end + end + + elsif current_interface && (line[0] == ?+ || line[0] == ?-) + mtype = line[0] + data = @cpp_result[i .. -1] + body_md = body_re.match(data) + + next if body_md.nil? + + rettype = body_md[1] ? body_md[1].delete("()") : "id" + retval = VarInfo.new(rettype, "", "") + args = [] + selector = "" + data = data[0 .. data.index(";")] + args_data = [] + + data.scan(args_re) do |x| + args_data << [$`, x, $'] + end + + variadic = false + args_data.each_with_index do |ary, n| + before, argname, argtype = ary + arg_nameless = (n > 0 && /\)\s*$/.match(before)) + argname = ":" if arg_nameless + realargname = nil + + if n < args_data.length - 1 + argtype.sub!(args_data[n + 1][2], "") + + if arg_nameless + argtype.sub!(/(\w+\s*)?\w+\s*:\s*$/, "") + else + unless argtype.sub!(/(\w+)\s+\w+:\s*$/) { |s| realargname = $1; "" } + # maybe the next argument is nameless + argtype.sub!(/\w+\s*:\s*$/, "") + end + end + else + argtype.sub!(/\s+__attribute__\(\(.+\)\)/, "") + + if arg_nameless + argtype.sub!(/\w+\s*;$/, "") + else + unless argtype.sub!(/(\w+)\s*;$/) { |s| realargname = $1; "" } + variadic = argtype.sub!(/,\s*\.\.\.\s*;/, "") != nil + argtype.sub!(/\w+\s*$/, "") if variadic + end + end + end + + selector << argname + realargname ||= argname.sub(/:/, "") + args << VarInfo.new(argtype, realargname, "") unless argtype.empty? + end + + selector = body_md[2] if selector.empty? + args << VarInfo.new("...", "vararg", "") if variadic + method = MethodInfo.new(retval, selector, line[0] == ?+, args, data) + + if current_category && current_interface == "NSObject" + (@informal_protocols[current_category] ||= []) << method + end + + @classes[current_interface] ||= {} + + if header.name == current_interface + @classes[current_interface].file = header.name + else + @classes[current_interface].file ||= header.name + end + + unless parent == current_interface || parent =~ /\s+/ || parent.nil? + @classes[current_interface].parent = parent + end + + (@classes[current_interface].methods ||= []) << method + end + i += size + end + end + + def structs (header) + re = /typedef\s+struct\s*\w*\s*((\w+)|\{([^{}]*(\{[^}]+\})?)*\}\s*([^\s]+))\s*(__attribute__\(.+\))?\s*;/ + i = 0 + body = nil + @cpp_result.scan(re).each do |m| + struct = { :name => m[4], :members => [] } + + unless struct[:name].nil? + if struct[:name][0, 1] == "*" + struct[:name].sub!("*", "") + end + end + + return_type = nil + stripped_return_type = nil + body = m[2] + + if m[2] + m[2].split(/,|;/).map do |i| + str, bytes = i.split(":", 2).map do |x| + x.strip + end + + var = get_var_info(str, true) + + if var + if var.return_type == "***dummy***" + var.return_type = return_type + var.stripped_return_type = stripped_return_type + else + return_type = var.return_type + stripped_return_type = var.stripped_return_type + end + + struct[:members] << { :name => var.name, :bytes => bytes, :declaredType => var.return_type, :type => "", :type64 => "" } unless str.empty? || str[0] == ?# + + names = [] + + tmp = struct[:members].collect do |member| + unless names.include?(member[:name]) + names << member[:name] + member + end + end + + struct[:members] = tmp.compact + end + end + end + + header.structs << struct if body + end + end + + def functions (header, inline = false) + if inline + inline_func_re = /(inline|__inline__)\s+((__attribute__\(\([^)]*\)\)\s+)?([\w\s\*<>]+)\s*\(([^)]*)\)\s*)\{/ + res = @cpp_result.scan(inline_func_re) + res.each do |x| + x.delete_at(0) + x.delete_at(1) + end + else + skip_inline_re = /(static)?\s(inline|__inline__)[^{;]+(;|\{([^{}]*(\{[^}]+\})?)*\})\s*/ + func_re = /(^([\w\s\*<>]+)\s*\(([^)]*)\)\s*)(__attribute__[^;]+)?;/ + res = @cpp_result.gsub(skip_inline_re, '').scan(func_re) + end + + funcs = res.map do |m| + orig, base, args = m + base.sub!(/^.*extern\s/, "") + func = constant?(base) + + if func + args = args.strip.split(",").map do |i| + constant?(i) + end + + next if args.any? do |x| + x.nil? + end + + args = [] if args.size == 1 && args[0].return_type == "void" + FunctionInfo.new(func, args, orig, inline) + end + end.compact + + if inline + header.inline_functions = funcs + else + header.functions = funcs + end + end + + def prepare (path) + @file_content = File.read(path) + @file_content.gsub!(%r{(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)}, ""); + @complete_cpp_result, @cpp_result = do_cpp(path, false, true, "") + end + + def scan (frameworks, headers) + @frameworks = frameworks + @headers = headers + + @frameworks.each do |framework| + framework.headers.each do |header| + prepare(header.path) + + imports(header) + cftypes(header) + externs(header) + constants(header) + enums(header) + structs(header) + typedefs(header) + functions(header) + functions(header, true) + defines(header) + methods(header) + function_pointer_types(header) + end + end + + @headers.each do |header| + prepare(header.path) + + imports(header) + cftypes(header) + externs(header) + constants(header) + enums(header) + structs(header) + typedefs(header) + functions(header) + functions(header, true) + defines(header) + methods(header) + function_pointer_types(header) + end + end + + def get_var_info (str, multi = false) + str.strip! + + return nil if str.empty? + + if str == "..." + VarInfo.new("...", "...", str) + else + str = "***dummy*** " + str if str[-1].chr == '*' || str.index(/\s/).nil? + tokens = multi ? str.split(',') : [str] + part = tokens.first + re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/ + m = re.match(part) + + if m + return nil if m[1].split(/\s+/).any? do |x| + ['end', 'typedef'].include?(x) + end + + m = m.to_a[1..-1].compact.map do |i| + i.strip + end + + m[0] += m[2] if m.size == 3 + m[0] = 'void' if m[1] == 'void' + + var = begin + VarInfo.new(m[0], m[1], part) + rescue + return nil + end + + if tokens.size > 1 + [var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }] + else + var + end + end + end + end + + def constant? (str, multi = false) + str.strip! + return nil if str.empty? + + if str == '...' + VarInfo.new('...', '...', str) + else + str << " dummy" if str[-1].chr == '*' or str.index(/\s/).nil? + tokens = multi ? str.split(',') : [str] + part = tokens.first + re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/ + m = re.match(part) + + if m + return nil if m[1].split(/\s+/).any? do |x| + ['end', 'typedef'].include?(x) + end + + m = m.to_a[1..-1].compact.map do |i| + i.strip + end + + m[0] += m[2] if m.size == 3 + m[0] = 'void' if m[1] == 'void' + + var = begin + VarInfo.new(m[0], m[1], part) + rescue + return nil + end + + if tokens.size > 1 + [var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }] + else + var + end + end + end + end + + class VarInfo + + attr_reader :name, :orig, :const, :type_modifier + attr_accessor :octype, :resolved_type, :resolved_type64, :return_type, :stripped_return_type + + def initialize (type, name, orig) + @return_type = type.clone + @name = name + @orig = orig + @const = false + @type_modifier = "" + + @return_type.gsub!(/\[[^\]]*\]/, "*") + + if @return_type =~ /\bconst\b/ + @const = true + @return_type.gsub!("const", "") + end + + if @return_type =~ /\b(in|out|inout|oneway|bycopy|byref)\b/ + + case $1 + when "in" + @type_modifier << "n" + when "out" + @type_modifier << "o" + when "inout" + @type_modifier << "N" + when "oneway" + @type_modifier << "w" + when "bycopy" + @type_modifier << "c" + when "byref" + @type_modifier << "r" + end + + @return_type.gsub!("#{$1}", "") + end + + @return_type.gsub!(/\s?\*\s?/, "*") + @return_type.gsub!(/^\(|\)$/, "") + @return_type.strip! + + t = type.gsub(/\b(__)?const\b/, "") + t.gsub!(/<[^>]*>/, '') + t.gsub!(/\b(in|out|inout|oneway|const)\b/, "") + t.gsub!(/\b__private_extern__\b/, "") + t.gsub!(/^\s*\(?\s*/, "") + t.gsub!(/\s*\)?\s*$/, "") + + raise "Empty type (was '#{type}')" if t.empty? + + @stripped_return_type = t + end + + def function_pointer? (function_pointer_types) + type = @function_pointer_types[@stripped_return_type] || @stripped_return_type + @function_pointer_type ||= FunctionPointerInfo.new_from_type(type) + end + + def <=>(x) + self.name <=> x.name + end + + def hash + @name.hash + end + + def eql? (o) + @name == o.name + end + end + + class FunctionInfo < VarInfo + + attr_reader :args, :argc + + def initialize (function, args, orig, inline = false) + super(function.return_type, function.name, orig) + + @args = args + @argc = @args.size + @variadic = false + + if @args[-1] && @args[-1].return_type == "..." + @argc -= 1 + @variadic = true + @args.pop + end + + @inline = inline + self + end + + def variadic? + @variadic + end + + def inline? + @inline + end + end + + class FunctionPointerInfo < FunctionInfo + def initialize (return_type, arg_types, orig) + args = arg_types.map do |x| + VarInfo.new(x, "", "") + end + + super(VarInfo.new(return_type, "", ""), args, orig) + end + + def self.new_from_type (type) + @cache ||= {} + info = @cache[type] + + return info if info + + tokens = type.split(/\(\*\)/) + return nil if tokens.size != 2 + + return_type = tokens.first.strip + rest = tokens.last.sub(/^\s*\(\s*/, "").sub(/\s*\)\s*$/, "") + + arg_types = rest.split(/,/).map do |x| + x.strip + end + + @cache[type] = self.new(return_type, arg_types, type) + end + end + + class MethodInfo < FunctionInfo + + attr_reader :selector + + def initialize (method, selector, is_class, args, orig) + super(method, args, orig) + + @selector = selector + @is_class = is_class + self + end + + def class_method? + @is_class + end + + def <=>(o) + @selector <=> o.selector + end + + def hash + @selector.hash + end + + def eql? (o) + @selector == o.selector + end + end + + def do_cpp (path, fails_on_error = true, do_64 = true, flags = "") + f_on = false + err_file = '/tmp/.cpp.err' + cpp_line = "#{CPP} #{CPPFLAGS} #{flags} #{do_64 ? '-D__LP64__' : ''} \"#{path}\" 2>#{err_file}" + complete_result = `#{cpp_line}` + + if $?.to_i != 0 && fails_on_error + $stderr.puts File.read(err_file) + File.unlink(err_file) + raise "#{CPP} returned #{$?.to_int/256} exit status\nline was: #{cpp_line}" + end + + result = complete_result.select do |s| + # First pass to only grab non-empty lines and the pre-processed lines + # only from the target header (and not the entire pre-processing result). + next if s.strip.empty? + m = %r{^#\s*\d+\s+"([^"]+)"}.match(s) + f_on = (File.basename(m[1]) == File.basename(path)) if m + f_on + end.select do |s| + # Second pass to ignore all pro-processor comments that were left. + /^#/.match(s) == nil + end.join + + File.unlink(err_file) + return [complete_result, result] + end +end + +class DStepGenerator + + VERSION = 1.0 + + attr_accessor :out_file, :scaner + + def initialize + @do_64bit = false + @frameworks = [] + @framework_paths = [] + @headers = [] + @import_directives = "" + @informal_protocols = [] + @classes = [] + @scaner = HeaderScaner.new + @scaner.do_64bit = @do_64bit + end + + def do_64bit + return @do_64bit + end + + def do_64bit= (do_64bit) + @do_64bit = do_64bit + @scaner.do_64bit = do_64bit + end + + def collect + scaner.scan(@frameworks, @headers) + @classes = scaner.classes + @informal_protocols = scaner.protocols + end + + def handle_framework (framework) + val = framework.name + path = framework_path(val) + + raise "Can't locate framework '#{val}'" if path.nil? + @framework_paths << File.dirname(path) + raise "Can't find framework '#{val}'" if path.nil? + + parent_path, name = path.scan(/^(.+)\/(\w+)\.framework\/?$/)[0] + + if @private + headers_path = File.join(path, "PrivateHeaders") + raise "Can't locate private framework headers at '#{headers_path}'" unless File.exist?(headers_path) + + headers = Dir.glob(File.join(headers_path, "**", "*.h")) + public_headers_path = File.join(path, "Headers") + public_headers = if File.exist?(public_headers_path) + HeaderScaner::CPPFLAGS << " -I#{public_headers_path} " + Dir.glob(File.join8(headers_path, "**", "*.h")) + else + [] + end + else + headers_path = File.join(path, "Headers") + raise "Can't locate public framework headers at '#{headers_path}'" unless File.exist?(headers_path) + public_headers = headers = Dir.glob(File.join(headers_path, "**", "*.h")) + end + + # We can't just "#import " as the main Framework header might not include _all_ headers. + # So we are tricking this by importing the main header first, then all headers. + header_basenames = (headers | public_headers).map do |x| + x.sub(/#{headers_path}\/*/, "") + end + + if idx = header_basenames.index("#{name}.h") + header_basenames.delete_at(idx) + header_basenames.unshift("#{name}.h") + end + + @import_directives = header_basenames.map do |x| + "#import <#{name}/#{x}>" + end.join("\n") + + @import_directives << "\n" + + @compiler_flags ||= "-F\"#{parent_path}\" -framework #{name}" + @cpp_flags ||= "" + @cpp_flags << "-F\"#{parent_path}\" " + + headers.each do |header| + header_file = HeaderFile.new + header_file.path = header + header_file.name = File.basename(header, File.extname(header)) + header_file.framework = framework.name + + framework.headers << header_file + end + + # Memorize the dependencies. + @dependencies = DStepGenerator.dependencies_of_framework(path) + end + + def framework_path (val) + return path if File.exist?(val) + + val += ".framework" unless /\.framework$/.match(val) + paths = ["/System/Library/Frameworks", "/Library/Frameworks", "#{ENV['HOME']}/Library/Frameworks"] + paths << "/System/Library/PrivateFrameworks" if @private + + paths.each do |dir| + path = File.join(dir, val) + return path if File.exist?(path) + end + + return nil + end + + def self.dependencies_of_framework (path) + @dependencies ||= {} + name = File.basename(path, ".framework") + path = File.join(path, name) + deps = @dependencies[path] + + if deps.nil? + deps = `otool -L "#{path}"`.scan(/\t([^\s]+)/).map do |m| + dpath = m[0] + next if File.basename(dpath) == name + next if dpath.include?("PrivateFrameworks") + next unless dpath.sub!(/\.framework\/Versions\/\w+\/\w+$/, "") + dpath + ".framework" + end.compact + + @dependencies[path] = deps + end + + return deps + end + + def compile_and_execute (code, enable_64bit = false) + compiler_line = "gcc " + src = File.new(unique_tmp_path("src", ".m"), "w") + src << code + src.close + + arch_flag = if enable_64bit + "-arch x86_64 -arch ppc64" + else + "-arch i386 -arch ppc" + end + + compiler_line << arch_flag + + bin = unique_tmp_path "bin" + log = unique_tmp_path "log" + + line = "#{compiler_line} -o #{bin} #{src.path} #{@compiler_flags}> #{log}" + + unless system(line) + msg = "Cannot compile Objective-C code ...aborting\nCommand was: #{line}\n\nLog:\n#{File.read(log)}\n\n" + $stderr << msg + File.delete src.path + raise msg + end + + result = `#{bin}` + + unless $?.success? + raise "Cannot execute compiled Objective-C code ... aborting\nCommand was: #{line}\nBinary is: #{bin}" + end + + File.delete bin + File.delete log + File.delete src.path + + return result + end + + def unique_tmp_path (base, extension = "", dir = Dir.tmpdir) + i = 0 + loop do + path = File.join(dir, "#{base}-#{Process.pid}-#{i}#{extension}") + return path unless File.exists?(path) + i += 1 + end + end + + def add_header (path) + header = HeaderFile.new + header.path = path + header.name = File.basename(path, File.extname(path)) + @import_directives << "#include <#{path}>\n" + @headers << header + end + + def add_framework (name) + framework = Framework.new + framework.name = name + handle_framework(framework) + #@import_directives << "#import <#{framework.name}/#{framework.name}.h>\n" + @frameworks << framework + end + + def collect_header_types (header, enable_64bit) + types = [] + + header.cftypes.each do |cftype| + types << cftype[:type] + end + + header.constants.each do |constant| + types << constant.stripped_return_type + end + + header.structs.each do |struct| + types << struct[:name] + + struct[:members].each do |member| + types << member[:declaredType] + end + end + + header.typedefs.each do |typedef| + types << typedef.stripped_return_type + end + + header.functions.each do |function| + types << function.stripped_return_type + + function.args.each do |arg| + types << arg.stripped_return_type + end + end + + types + end + + def collect_classes_types (enable_64bit) + types = [] + + @classes.each do |clazz, value| + value.methods.each do |method| + types << method.stripped_return_type + + method.args.each do |arg| + types << arg.stripped_return_type + end + end + end + + types + end + + def collect_informal_protocols_types (enable_64bit) + types = [] + + @informal_protocols.each do |name, methods| + methods.each do |method| + types << method.stripped_return_type + + method.args.each do |arg| + types << arg.stripped_return_type + end + end + end + + types + end + + def resolve_header_types (header, enable_64bit, resolved_types, x) + i = x + + header.cftypes.each do |cftype| + cftype[enable_64bit ? :type64 : :type] = resolved_types[i] + i += 1 + end + + header.constants.each do |constant| + constant.resolved_type = resolved_types[i] unless enable_64bit + constant.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + end + + header.structs.each do |struct| + struct[enable_64bit ? :type64 : :type] = resolved_types[i] + i += 1 + + struct[:members].each do |member| + member[enable_64bit ? :type64 : :type] = resolved_types[i] + i += 1 + end + end + + header.typedefs.each do |typedef| + typedef.resolved_type = resolved_types[i] unless enable_64bit + typedef.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + end + + header.functions.each do |function| + function.resolved_type = resolved_types[i] unless enable_64bit + function.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + + function.args.each do |arg| + arg.resolved_type = resolved_types[i] unless enable_64bit + arg.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + end + end + + i + end + + def resolve_methods_types (enable_64bit, resolved_types, x) + i = x + + @classes.each do |clazz, value| + value.methods.each do |method| + method.resolved_type = resolved_types[i] unless enable_64bit + method.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + + method.args.each do |arg| + arg.resolved_type = resolved_types[i] unless enable_64bit + arg.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + end + end + end + + i + end + + def resolve_informal_protocols_types (enable_64bit, resolved_types, x) + i = x + + @informal_protocols.each do |name, methods| + methods.each do |method| + method.resolved_type = resolved_types[i] unless enable_64bit + method.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + + method.args.each do |arg| + arg.resolved_type = resolved_types[i] unless enable_64bit + arg.resolved_type64 = resolved_types[i] if enable_64bit + i += 1 + end + end + end + + i + end + + def resolve_types (enable_64bit = false) + code = "#include \n" + code << @import_directives + types = [] + + @frameworks.each do |framework| + framework.headers.each do |header| + types << collect_header_types(header, enable_64bit) + end + end + + @headers.each do |header| + types << collect_header_types(header, enable_64bit) + end + + types << collect_classes_types(enable_64bit) + types << collect_informal_protocols_types(enable_64bit) + + code << "int main () \n{\n" + types.flatten! + + types.each do |type| + code << ' printf("%s\n", ' + "@encode(#{type}));\n" + end + + code << " return 0;\n}" + + resolved_types = [] + + compile_and_execute(code, enable_64bit).split("\n").each do |line| + resolved_types << line + end + + i = 0 + + @frameworks.each do |framework| + framework.headers.each do |header| + i = resolve_header_types(header, enable_64bit, resolved_types, i) + end + end + + @headers.each do |header| + i = resolve_header_types(header, enable_64bit, resolved_types, i) + end + + i = resolve_methods_types(enable_64bit, resolved_types, i) + i = resolve_informal_protocols_types(enable_64bit, resolved_types, i) + end + + def generate_header (xml, header) + xml.file :name => header.name do + header.imports.each do |import| + xml.import import + end + + header.defines.each do |define| + xml.define define + end + + header.cftypes.each do |cftype| + xml.cftype cftype + end + + header.constants.each do |constant| + xml.constant :name => constant.name, :declaredType => constant.return_type, :type => constant.resolved_type, :type64 => constant.resolved_type64, :const => constant.const + end + + header.enums.each do |enum| + xml.enum :name => enum[:name] do + enum[:members].each do |member| + xml.member member + end + end + end + + header.structs.each do |struct| + xml.struct :name => struct[:name], :type => struct[:type], :type64 => struct[:type64] do + struct[:members].each do |member| + xml.member member + end + end + end + + header.typedefs.each do |typedef| + xml.typedef :name => typedef.name, :declaredType => typedef.return_type, :type => typedef.resolved_type, :type64 => typedef.resolved_type64, :const => typedef.const + end + + header.functions.each do |function| + xml.function :name => function.name, :inline => function.inline?, :variadic => function.variadic? do + function.args.each do |arg| + xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier + end + + xml.returnValue :declaredType => function.return_type, :type => function.resolved_type, :type64 => function.resolved_type64, :const => function.const, :typeModifier => function.type_modifier + end + end + end + end + + def generate_classes (xml) + @classes.each do |clazz, value| + xml.class :name => clazz, :parent => value.parent, :file => value.file do + value.methods.each do |method| + xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do + method.args.each do |arg| + xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier + end + + xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier + end + end + end + end + end + + def generate_informal_protocols (xml) + @informal_protocols.each do |name, methods| + xml.informalProtocol :name => name do + methods.each do |method| + xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do + method.args.each do |arg| + xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier + end + + xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier + end + end + end + end + end + + def generate + resolve_types + resolve_types(true) if @do_64bit + + file = STDOUT if @out_file == nil + file = File.open @out_file, "w" unless @out_file == nil + + xml = XmlMarkup.new(:target => file, :indent => 4) + xml.instruct! + + xml.dstep :xmlns => "http://www.dsource.org/projects/dstep", "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation" => "http://www.dsource.org/projects/dstep/trunk/scripts/dstep.xsd" do + @frameworks.each do |framework| + xml.framework :name => framework.name do + framework.headers.each do |header| + generate_header(xml, header) + end + end + end + + @headers.each do |header| + generate_header(xml, header) + end + + generate_classes(xml) + generate_informal_protocols(xml) + end + + file.close unless file == STDOUT + end +end + +def die (*msg) + $stderr.puts msg + exit 1 +end + +if __FILE__ == $0 + dstep_gen = DStepGenerator.new + + OptionParser.new do |opts| + opts.banner = "Usage: #{File.basename(__FILE__)} [options] " + opts.separator "" + opts.separator "Options:" + + opts.on("-f", "--framework FRAMEWORK", "Generate metadata for the given framework.") do |opt| + dstep_gen.add_framework(opt) + end + + opts.on(nil, "--64-bit", "Write 64-bit annotations.") do + dstep_gen.do_64bit = true + end + + opts.on("-o", "--output FILE", "Write output to the given file.") do |opt| + die "Output file can't be specified more than once" if dstep_gen.out_file + dstep_gen.out_file = opt + end + + # opts.on("-d", "--output-dir PATH", "Write ouptut to the given paht, use this with the --framework option") do |opt| + # die "Output directory can't be specified more than once" if dstep_gen.out_dir + # dstep_gen.out_dir = opt + # end + + + + help_msg = "Use the `-h' flag or for help." + + opts.on("-h", "--help", "Show this message.") do + puts opts, help_msg + exit + end + + opts.on('-v', '--version', 'Show version.') do + puts DStepGenerator::VERSION + exit + end + + opts.separator "" + + if ARGV.empty? + die opts.banner + else + begin + opts.parse!(ARGV) + + ARGV.each do |header| + dstep_gen.add_header(header) + end + + dstep_gen.collect + dstep_gen.generate + rescue => e + msg = e.message + msg = "Internal error" if msg.empty? + + die msg, opts.banner, help_msg + end + end + end +end \ No newline at end of file