Mercurial > projects > dstep
diff sciprts/dgen.rb @ 0:c7db221de6e8
First upload of dgen.rb and dstepgen.rb
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Sat, 31 Jan 2009 17:22:44 +0100 |
parents | |
children |
line wrap: on
line diff
--- /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 <br />\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] <dstep files...>" + 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