view scripts/dgen.rb @ 13:4f583f7e242e

Added a constructor for every init method
author Jacob Carlborg <doob@me.com>
date Mon, 03 Aug 2009 15:21:59 +0200
parents 07194b026fa4
children 7ff919f595d5
line wrap: on
line source

#!/usr/bin/env ruby

##
# Copyright:: Copyright (c) 2009 Jacob Carlborg.
# Author:: Jacob Carlborg
# Version:: Initial created: 2009
# License:: [Boost Software License 1.0]http://www.boost.org/LICENSE_1_0.txt
#

require "rubygems"
gem "xml-simple"
require "xmlsimple"
require "optparse"
require "date"

# 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, :c_constants, :d_constants, :d_constants_static_this, :defines, 
			  			:enums, :enums_gnu, :needs_type_encoding, :functions, :c_functions, :function_pointers, :function_wrappers, :imports, :path, :structs, :typedefs) do
	def initialize
		self.name = ""
		self.cftypes = []
		self.constants = []
		self.defines = []
		self.enums = []
		self.enums_gnu = []
		self.needs_type_encoding = false
		self.framework = ""
		self.functions = []
		self.c_functions = []
		self.function_pointers = []
		self.function_wrappers = []
		self.imports = []
		self.path = ""
		self.structs = []
		self.typedefs = []
		self.d_constants_static_this = []
	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 = {}
		@interfaces = {}
		@interfaces2 = []
		@templates = {}
		@copyright = nil
		@c_constants = []
		@d_constants = []
		@d_constants_static_this = []
		@files = []
		@frameworks = []
		@function_wrappers = []
		@c_functions = []
		@headers = []
		@package = "dstep"
		@needs_bridge = false
	end	
	
	# Generates the D code from the xml metadata
	def generate_code
		@files.each do |dstep_file|
			xml = XmlSimple.xml_in(dstep_file)
			
			@needs_bridge = !xml.protocol.nil? || !xml.category.nil? || !xml["class"].nil?
			
			interfaces(xml.protocol) unless xml.protocol.nil?
			templates(xml.category) unless xml.category.nil?
			classes(xml["class"]) unless xml["class"].nil?

			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.c_constants = c_constants 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, header.enums_gnu, header.needs_type_encoding = enums(file.enum, header.name) unless file.enum.nil?
						header.functions = functions(file.function) unless file.function.nil?
						header.c_functions = c_functions unless file.function.nil?
						header.function_pointers = function_pointers(file.functionPointer) unless file.functionPointer.nil?
						header.function_wrappers = function_wrappers unless file.function.nil?
						header.imports = imports(header, 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

				files.each do |file|
					header = HeaderFile.new
					header.name = file.name
					header.constants = constants(file.constant) unless file.constant.nil?
					header.c_constants = c_constants 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, header.enums_gnu, header.needs_type_encoding = enums(file.enum) unless file.enum.nil?
					header.functions = functions(file.function) unless file.function.nil?
					header.function_pointers = function_pointers(file.functionPointer) unless file.functionPointer.nil?
					header.c_functions = c_functions unless file.function.nil?
					header.function_wrappers = function_wrappers unless file.function.nil?
					header.imports = imports(header, 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					
		end
	end
	
	# Outputs the generate D code
	def output_code
		@frameworks.each do |framework|
			framework_path = framework_path = "#{@out_dir}/#{@package}/#{get_identifier(get_framework_name(framework.name.downcase))}" unless sub_framework?(framework.name.downcase)
			framework_path = framework_path = "#{@out_dir}/#{@package}/#{get_identifier(get_parent_framework_name(framework.name.downcase))}/#{get_identifier(get_framework_name(framework.name.downcase))}" if sub_framework?(framework.name.downcase)
			
			FileUtils.mkdir_p(framework_path) unless File.exist?(framework_path)
			
			framework.files.each do |header|
				file_path = "#{framework_path}/#{get_identifier(header.name)}"
				bindings_file_path = file_path + "_bindings.d"
				file_path << ".d"
				mod = "#{@package}.#{get_identifier(get_framework_name(framework.name.downcase))}.#{get_identifier(header.name)}" unless sub_framework?(framework.name.downcase)
				mod = "#{@package}.#{get_identifier(get_parent_framework_name(framework.name.downcase))}.#{get_identifier(get_framework_name(framework.name.downcase))}.#{get_identifier(header.name)}" if sub_framework?(framework.name.downcase)
				
				File.open(file_path, "w") do |file|
					file << copyright
					file << "module #{mod};"
					file << header.imports
					file << "import bindings = #{mod}_bindings".nl.nl(false) unless header.d_constants.nil? || header.function_wrappers.nil?
					file << header.defines
					file << header.typedefs
					file << header.cftypes
					file << header.function_pointers
					file << header.c_constants
					file << header.d_constants
					file << header.enums_gnu if header.needs_type_encoding
					file << header.enums
					file << header.structs
					
					unless header.d_constants_static_this.length == 0
						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
					
					interfaces = get_interfaces(header.name)
					
					interfaces.each do |interface, value|
						file << value.code.nl(false)
						@interfaces.delete(interface)
					end
					
					templates = get_templates(header.name)
					
					templates.each do |template, value|
						file << value.code.nl(false)
						@templates.delete(template)
					end
					
					file << header.function_wrappers
					file << header.c_functions							
				end
				
				File.open(bindings_file_path, "w") do |file|
					file << "module "
					file << mod
					file << "_bindings;"
					file << header.imports.nl(false)
					file << header.constants
					file << header.functions
				end unless header.d_constants.nil? || header.function_wrappers.nil?
			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}/#{get_identifier(header.name)}"
			bindings_file_path = header_path + "_bindings.d"
			header_path << ".d"
			mod = "#{@package}.#{get_identifier(header.name)}"
			
			File.open(header_path, "w") do |file|
				file << copyright
				file << "module #{mod};"
				file << header.imports
				file << "import bindings = #{mod}_bindings".nl.nl(false) unless header.d_constants.nil? || header.function_wrappers.nil?
				file << header.defines
				file << header.typedefs
				file << header.cftypes
				file << header.function_pointers
				file << header.c_constants
				file << header.d_constants
				file << header.enums_gnu if header.needs_type_encoding
				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
				
				interfaces = get_interfaces(header.name)
				
				interfaces.each do |interface, value|
					file << value.code.nl(false)
					@interfaces.delete(interface)
				end
				
				templates = get_templates(header.name)
				
				templates.each do |template, value|
					file << value.code.nl(false)
					@templates.delete(template)
				end

				file << header.function_wrappers
				file << header.c_functions	
			end
				
			File.open(bindings_file_path, "w") do |file|
				file << "module "
				file << mod
				file << "_bindings;"
				file << header.imports.nl(false)
				file << header.constants
				file << header.functions
			end unless header.d_constants.nil? || header.function_wrappers.nil?				
		end
		
		@classes.each do |clazz, value|
			class_path = "#{package_path}/#{get_identifier(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.\n"
		str << " * Authors: Jacob Carlborg\n"
		str << " * Version: Initial created: #{initial_created} \n"
		str << " * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.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 get_identifier(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
	
	# Gets the interfaces that belongs to the given file
	def get_interfaces (name)		
		interfaces = @interfaces.find_all do |interface, value|
			value.file == name
		end
		
		return interfaces
	end
	
	# Gets the templates that belongs to the given file
	def get_templates (name)		
		templates = @templates.find_all do |template, value|
			value.file == name
		end
		
		return templates
	end
	
	# Generates the D code for the classes
	def classes (classes)
		classes.each do |clazz|
			str = StringIO.new
			protocols = []
			
			str << "class "
			
			if clazz == ""
				str << get_identifier(clazz.name).nl(false)
			else
				str << get_identifier(clazz.name)
				
				unless clazz.parent == ""
					str << " : "
					str << get_identifier(clazz.parent)
				end
				
				unless clazz.protocols.length == 0
					protocols = clazz.protocols.split(",")
					
					str2 = StringIO.new
					
					str2 << ", " unless clazz.parent.length == 0
					str2 << " : " if clazz.parent.length == 0
					
					protocols.each do |protocol|
						str2 << "I"
						str2 << get_identifier(protocol)
						str2 << ", "
					end
					
					str << str2.string[0 .. -3]
				end
				
				str << "\n"
			end			
			
			str << "{".nl(false)
			str << "mixin ObjcWrap".indent.nl
			
			templates_for_class(clazz.name).each do |template, value|
				str << "mixin #{get_identifier("T" + template)}".indent.nl
			end
			
			str << "\n"

			str << methods(clazz.method, clazz.name)
			str << "\n" if protocols.length > 0
			
			# implement the interfaces/protocols
			protocols.each_with_index do |protocol, i|
				interface = interface_for_protocol(protocol)
				
				unless interface.nil?
					str << methods(interface.method, interface.name)
					str << "\n" unless i == protocols.length - 1
				end
			end			
			
			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 = get_identifier(clazz.file)
		end
	end
	
	# Generates the D code for the interfaces
	def interfaces (interfaces)
		interfaces.each do |interface|
			str = StringIO.new
			
			str << "interface " # prepend I to the interface name, because it can be the same as a class name
			
			if interface == ""
				str << get_identifier("I" + interface.name).nl(false)
			else
				str << get_identifier("I" + interface.name)
				
				unless interface.parent == ""
					str << " : "
					str << get_identifier(interface.parent)
				end
				
				unless interface.protocols.length == 0
					protocols = interface.protocols
					protocols = protocols.split(",")
					
					str2 = StringIO.new
					
					str2 << ", " unless interface.parent.length == 0
					str2 << " : " if interface.parent.length == 0
					
					protocols.each do |protocol|
						str2 << "I"
						str2 << get_identifier(protocol)
						str2 << ", "
					end
					
					str << str2.string[0 .. -3]
				end
				
				str << "\n"
			end
			
			str << "{".nl(false)
			str << interface_methods(interface.method, interface.name)
			str << "}".nl(false)
			
			@interfaces[interface.name] ||= {}
			@interfaces[interface.name].code = str.string
			framework = get_framework(interface.file)
			@interfaces[interface.name].framework = framework unless framework.nil?
			@interfaces[interface.name].file = get_identifier(interface.file)
			@interfaces2 << interface
		end
	end
	
	# Generates the D code for the templates
	def templates (templates)
		templates.each do |template|
			str = StringIO.new

			str << "template "			
			str << get_identifier("T" + template.name)
			str << " ()".nl(false)
			str << "{".nl(false)
			str << interface_methods(template.method, template.name) if template["class"] == "NSObject"
			str << methods(template.method, template.name) unless template["class"] == "NSObject"
			str << "}".nl(false)

			@templates[template.name] ||= {}
			@templates[template.name].code = str.string
			framework = get_framework(template.file)
			@templates[template.name].framework = framework unless framework.nil?
			@templates[template.name].file = get_identifier(template.file)
			@templates[template.name][:class] = get_identifier(template["class"])
		end
	end
	
	# Generates the D code for the constants/globals
	def constants (constants)
		return "" if constants.length == 0
			
		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 }
				
				str << "package ".indent(2)
				str << "const id" if const
				str << "id" unless const				
				str << " "
				str << get_identifier(constant["name"]).nl
			else
				@c_constants << { :name => constant.name.dup, :type => type, :const => const }
			end
		end
		
		str << "}".indent.nl(false)
		str << "}".nl(false).nl(false)
		
		str.string
	end
	
	def c_constants
		return "" if @c_constants.length == 0
		
		str = StringIO.new
		
		str << "extern (C)".nl(false)
		str << "{".nl(false)
		str << "extern".indent.nl(false)
		str << "{".indent.nl(false)
		
		@c_constants.each do |constant|
			if constant.const
				str << "const ".indent(2)
				str << constant.type
			else
				str << constant.type.indent(2)
			end
			
			str << " "
			str << get_identifier(constant.name).nl
		end
		
		str << "}".indent.nl(false)
		str << "}".nl(false).nl(false)
		
		@c_constants.clear
		str.string
	end
	
	
    # Generates the D code for the constants that D can't handle directly, like classes
	def d_constants
		return "" if @d_constants.length == 0
			
		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 << get_identifier(constant.type)
			str << " "			
			str << get_identifier(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
		return "" if @d_constants_static_this.length == 0
		
		str = StringIO.new
		
		@d_constants_static_this.each do |constant|
			str << constant.name.indent
			str << " = new "
			str << get_identifier(constant.type)
			str << "("
			str << "bindings."
			str << get_identifier(constant.name)
			str << ")".nl
		end
		
		@d_constants_static_this.clear
		str.string
	end	
	
	# Generates the D code for the enums
	def enums (enums, header_name)
		return "" if enums.length == 0
		
		str = StringIO.new
		str_gnu = StringIO.new
		consts = []
		
		enums.each do |enum|
			localConsts = []
			str_gnu2 = StringIO.new
			str << "enum"
			needs_type_encoding = false
					
			if enum.name.length == 0
				str << "\n{".nl(false)
				
				needs_type_encoding, localConsts = enum_helper(enum, str, str_gnu2, header_name)			
			else
				str << " "
				str << get_identifier(enum["name"]).nl(false)
				str << "{".nl(false)
				
				needs_type_encoding, localConsts = enum_helper(enum, str, str_gnu2, header_name)
			end			
			
			str << "\n}".nl(false).nl(false)
			
			# if needs_type_encoding
			# 	str_gnu << "// This is needed otherwise the enums will fail compiling with gdc\n"
			# 	str_gnu << "version (GNU)\n{\n"
			# 	str_gnu << "private\n".indent
			# 	str_gnu << "{\n".indent
			# 	str_gnu << str_gnu2.string				
			# 	str_gnu << "\n"
			# 	str_gnu << "}".indent
			# 	str_gnu << "\n}".nl(false).nl(false)
			# end
			
			consts << localConsts 
		end
		
		consts.flatten!
		
		if @needs_type_encoding && consts.length > 0
			str_gnu << "// This is needed otherwise the enums will fail compiling with gdc\n"
			str_gnu << "version (GNU)\n{\n"
			str_gnu << "private\n".indent
			str_gnu << "{\n".indent
			
			consts.each do |const|
				str_gnu << "const __".indent(2)
				str_gnu << const.name
				str_gnu << ' = getOSType!("'
				str_gnu << const.value
				str_gnu << '")'.nl
			end
			
			str_gnu << "}".indent
			str_gnu << "\n}".nl(false).nl(false)
		end
		
		needs_type_encoding = @needs_type_encoding
		@needs_type_encoding = false
		
		return str.string, str_gnu.string, needs_type_encoding
	end
	
	def enum_helper (enum, str, str_gnu, header_name)	
		needs_type_encoding = false
		consts = []
			
		enum.member.each_with_index do |member, i|
			str << get_identifier(member.name).indent			
			
			if member.value.length > 0
				needs_type_encoding = true if member.value[0, 1] == "'"
				@needs_type_encoding = true if needs_type_encoding
				str << " = "	
				
				if member.value[0, 1] == "'"					
					str << 'getOSType!("'
					str << member.value[1 ... -1]
					str << '")'
					
					consts << { :name => get_identifier(member.name), :value => member.value[1 ... -1] }
					# str_gnu << "const __".indent(2)
					# str_gnu << get_identifier(member.name)
					# str_gnu << " = "					
					# str_gnu << 'getOSType!("'
					# str_gnu << member.value[1 ... -1]
					# str_gnu << '")'.nl
				else
					str << member.value
				end
			end
			
			str << ",".nl(false) unless i == enum.member.length - 1
		end
		
		return needs_type_encoding, consts
	end
		
	# Generates the D code for the function/method args
	def args (args, variadic, method = false)
		return "" if args.nil?
		
		str = StringIO.new
		
		args.each do |arg|
			
			if method || arg.type != "@"
				type = get_type(arg.type, arg.type64, arg.declaredType)
			else
				type = "id"
			end
			
			str << type
			str << " " unless arg.name.nil?
			str << get_identifier(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)
		return "" if functions.length == 0
		
		str = StringIO.new
		wrapper_needed = false
		
		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
				str << functions_helper(function, wrapper_needed)
			else
				function.arg.each do |arg|
					if (arg.type || arg.type64) == "@"
						@function_wrappers << function
						wrapper_needed = true
						break
					end
				end unless function.arg.nil?
				
				str << functions_helper(function, wrapper_needed) if wrapper_needed
				@c_functions << function unless wrapper_needed
			end
		end
		
		str << "}"
		str.string
	end
	
	def function_pointers (function_pointers)
		return "" if function_pointers.length == 0
		
		str = StringIO.new
		
		str << "extern (C)\n{".nl(false)
		
		function_pointers.each do |fp|
			str << "alias ".indent
			str << get_type(fp.returnValue[0].type, fp.returnValue[0].type64, fp.returnValue[0].declaredType)
			str << " function ("
			str << args(fp.arg, fp.variadic == "true")
			str << ") "
			str << get_identifier(fp.name).nl
		end
		
		str << "}".nl(false).nl(false)
		
		str.string
	end	
	
	def c_functions	
		return "" if @c_functions.length == 0
		
		str = StringIO.new
		
		str << "extern (C)".nl(false)
		str << "{".nl(false)
		
		@c_functions.each do |function|
			str << functions_helper(function, false)
		end
		
		str << "}"
		
		@c_functions.clear
		str.string
	end
	
	
	# A helper function that generates the D code for the functions
	def functions_helper (function, wrapper_needed)
		str = StringIO.new
		
		str << ("private " + build_function(function)).indent if wrapper_needed
		str << build_function(function).indent unless wrapper_needed

		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
		return "" if @function_wrappers.length == 0
			
		str = StringIO.new
		
		@function_wrappers.each do |function|
			return_type = get_type(function.returnValue[0].type, function.returnValue[0].type64, function.returnValue[0].declaredType)
			variadic = function.variadic == "true"

			str << build_function(function, true)[0 ... -2].nl(false)
			str << "{".nl(false)
			str << "return Bridge.invokeObjcFunction!(".indent
			str << return_type
			str << ", bindings."
			str << function.name

			function.arg.each do |arg|
				str << ", "
				str << get_type(arg.type, arg.type, arg.declaredType)
			end unless function.arg.nil?

			unless function.arg.nil?
				str << ")"
				str << "("
			end

			function.arg.each_with_index do |arg, i|
				str << arg.name
				str << ", " unless i == function.arg.length - 1
			end unless function.arg.nil?

			str << ")".nl

			str << "}".nl(false).nl(false)
		end
		
		@function_wrappers.clear		
		str.string
	end
	
	# Generates the D code for the imports
	def imports (header, imports, filename, framework_name = nil)
		return "" if imports.length == 0
		
		str = StringIO.new
		
		imports.each do |import|			
			str << "import #{@package}."
			import = import.gsub("/", ".").gsub("Frameworks", "").gsub(".framework", "").gsub("Headers", "").gsub(/\.{2,}/, ".").nl
			
			splits = import.split('.')
			import = ""
			
			splits[0 .. -2].each do |s|
				import << get_identifier(s.downcase)
				import << '.'
			end
			
			str << import
			str << splits[-1 .. -1]
		end
		
		str << "import dstep.objc.bridge.TypeEncoding".nl if header.needs_type_encoding && !@needs_bridge
		
		if @needs_bridge
			str << "import dstep.objc.bridge.Bridge".nl
			str << "import dstep.objc.bridge.TypeEncoding".nl if header.needs_type_encoding
			str << "import dstep.objc.objc : id".nl
		end
		
		str << "\n\n"
		str = str.string.sort.to_s
		str << "\n"
		
		return "\n\npublic:" << str if filename == get_framework_name(framework_name)
		return str
	end
	
	# Generates the D code for the methods
	def methods (methods, class_name)
		return "" if methods.length == 0
		
		str = StringIO.new
		
		methods.each do |method|				
			return_type = get_type(method.returnValue[0].type, method.returnValue[0].type64, method.returnValue[0].declaredType)
			variadic = method.variadic == "true"
			static = method.classMethod == "true"
			
			index = 0
			declaration = build_function(method, true, static)[0 ... -2].indent.nl(false)
			index = declaration.index_of(" ") if static
			index = declaration.index_of(" ", index)
			name = get_method_name(method.selector)
			
			str << (declaration[0 .. index] + name + declaration[index .. -1]).gsub(/ +/, " ")
			str << "{".indent.nl(false)
			
			if static
				str << "return invokeObjcSelfClass!(".indent(2)
				str << return_type
			else
				if return_type == class_name
					str << "id result = invokeObjcSelf!(".indent(2)
					str << "id"
				else
					str << "return invokeObjcSelf!(".indent(2)
					str << return_type
				end
			end
			
			str << ', "'
			str << method.selector
			str << '"'
			
			method.arg.each do |arg|
				str << ", "
				str << get_type(arg.type, arg.type, arg.declaredType)
			end unless method.arg.nil?
			
			unless method.arg.nil?
				str << ")"
				str << "("
			end
			
			method.arg.each_with_index do |arg, i|
				str << get_identifier(arg.name)
				str << ", " unless i == method.arg.length - 1
			end unless method.arg.nil?			
			
			if return_type == class_name
				str << "return result is this.objcObject ? this : (result !is null ? new #{return_type}(result) : null);"
			else
				str << ")".nl
			end
			
			str << "}".indent.nl(false).nl(false)
			
			if name.length >= 4 && name[0 ... 4] == "init" && name != "initialize"
				str << ("this" + declaration[index .. -1]).gsub(/ +/, " ").indent
				str << "{".indent.nl(false)
				str << 'objcObject = Bridge.invokeObjcClassMethod!(id, "alloc")(objcClass)'.indent(2).nl
				str << 'id result = Bridge.invokeObjcMethod!(id, "'.indent(2)
				str << method.selector
				str << '"'
				
				method.arg.each do |arg|
					str << ", "
					str << get_type(arg.type, arg.type, arg.declaredType)
				end unless method.arg.nil?
				
				str << ")(objcObject"
				
				method.arg.each do |arg|
					str << ", "
					str << get_identifier(arg.name)
				end unless method.arg.nil?
				
				str << ")".nl.nl(false)
				
				str << "if (result)".indent(2).nl(false)
				str << "objcObject = ret".indent(3).nl.nl(false)
				str << "dObject = this".indent(2).nl
				str << "}".indent.nl(false).nl(false)
			end
		end
		
		str.string[0 .. -2]
	end
	
	# Generates the D code for the interface methods
	def interface_methods (methods, interface_name)
		return "" if methods.length == 0
		
		str = StringIO.new
		
		methods.each do |method|
			return_type = get_type(method.returnValue[0].type, method.returnValue[0].type64, method.returnValue[0].declaredType)
			
			variadic = method.variadic == "true"
			static = method.classMethod == "true"
			
			index = 0
			declaration = build_function(method, true, static)[0 ... -2].indent.nl(false)
			index = declaration.index_of(" ", index)
			index = declaration.index_of(" ", index + 1) if static
			str << ((declaration[0 .. index] + get_method_name(method["selector"]) + declaration[index .. -1]).gsub(/ +/, " "))[0 .. -2]
			str << ";\n"
		end
		
		str.string
	end	
	
	# Generates the D code for the structs
	def structs (structs)
		return "" if structs.length == 0
		
		str = StringIO.new
		
		structs.each do |struct|
			str << "struct "
			str << get_identifier(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 << get_identifier(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)
		return "" if typedefs.length == 0
		
		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
			
			if struct				
				unless type.name == typedef.name
					declaredType = typedef.declaredType
					declaredType = declaredType[7 .. -1] if declaredType.length > 7 && declaredType[0 ... 6] == "struct"
					
					unless declaredType == typedef.name
						str << "alias "
						str << get_identifier(declaredType)
						str << " "
						str << get_identifier(typedef.name).nl
					end
					
					# str << "alias "				
					# str << get_identifier(type.name)
					# str << "*" if typedef.declaredType =~ /\*{1}/
					# str << " "
					# str << get_identifier(typedef.name).nl
				end
			else
				str << "alias "
				str << type
				str << " "
				str << get_identifier(typedef.name).nl
			end unless type.nil?
		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"
			when "UniChar"; return "wchar"
			when "BOOL"; return "bool"
			when "CGFloat"; return type
			when "NSInteger"; return type
			when "NSUInteger"; return type
			when "IMP"; return type;
				
			when "unichar*"
			when "UniChar*"; return "wchar*"
			when "BOOL*"; return "bool*"
			when "CGFloat*"; return type
			when "NSInteger*"; return type
			when "NSUInteger*"; return type
				
			when "unichar**"
			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(":")
		get_identifier(selector[0 ... i])
	end
	
	def get_matching_close_char (char)
		case char
		when "{"; return "}"
		when "("; return ")"
		when "["; return "]"
		else
			char
		end
	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)
		declared_type = "I" + $1 if declared_type =~ /\w+\s*<(.+)>/
		declared_type.gsub!(/\(|\)/, "")
				
		return get_identifier(declared_type) if type.nil? && type64.nil?
			
		t = check_declared_type(declared_type)
		return t unless t.nil?
		
		unless type64 == "" || type64.nil?
			return get_identifier(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 "@"
			unless declared_type.nil?
				t = declared_type.gsub(/\*+/, "")
				return t == "id" ? "Object" : t
			end
			
			raise "No declared type given"
		else
			return declared_type if type =~ /\{/
			
			case type[0, 1]
			when "["
				str = ""
				t = type[1 ... -1]
				count = $1 if t =~ /(\d+)/				
				t = $'
				
				t64 = ""
				
				unless type64.nil?
					t64 = type64[1 ... -1]
					count = $1 if t64 =~ /(\d+)/				
					t64 = $'
				else
					t64 = t
				end
				
				str = get_type(t, t64, declared_type)
				str << "[#{count}]"
			
				return get_identifier(str)
			when "("
				resolved_types = []
				types = $2 if type =~ /\((.+)=(.+)\)/
				types64 = $2 if type64 =~ /\((.+)=(.+)\)/
				i = 0
				
				while i < types.length
					t = types[i, 1]
					t64 = types64.nil? ? t : types64[i, 1]
					
					index = t =~ /(\(|\[|\{)/
					
					unless index.nil?
						x = types.index(get_matching_close_char($1), index)
						t = types[index .. x]
						t64 = types64.nil? ? t : types64[index .. x]
						i += x - index
					end
						
					resolved_types << get_type(t, t64, declared_type)
					i += 1			
				end unless types.nil?
				
				get_identifier(resolved_types)
			when "^"
				t = type[1 .. -1]
				t64 = type64.nil? ? t : type64[1 .. -1]
				get_identifier(get_type(t, t64, declared_type).dup + "*")
				
				# if type == "^@"
				# 	return get_identifier(get_type(type[1 .. -1], type64[1 .. -1], declared_type).dup + "*")
				# elsif type.length > 2 && type[0 ... 2] == "^^"
				# 	return get_identifier(get_type(type[2 .. -1], type64[2 .. -1], declared_type).dup + "**")
				# elsif type == "^?" # assuming function pointer
				# 	tmp = cfp_to_dfp(type)
				# 	return get_identifier(tmp) unless tmp.nil?
				# end
				# 
				# if !type.nil? && !type64.nil?
				# 	t = get_type(type[1 .. -1], type64[1 .. -1], declared_type).dup
				# 	
				# 	return get_identifier(t) if t =~ /\*/
				# 	return get_identifier(t + "*") if t !~ /\*/
				# elsif !type.nil?
				# 	t = get_type(type[1 .. -1], type64, declared_type).dup << "*"
				# 	
				# 	return get_identifier(t) if t =~ /\*/
				# 	return get_identifier(t + "*") if t !~ /\*/
				# end			
			when "{"
				return get_identifier(declared_type)
			end
		end
		
		return get_identifier(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 = []
		
			if type =~ /\^{0,}\{(.{0,})=(.{0,})\}/
				name = get_identifier($1)
				types = get_identifier($2)
			elsif type =~ /\^{0,}\((.{0,})=(.{0,})\)/
				name = get_identifier($1)
				types = get_identifier($2)
			end
		
			unless types.nil?
				types.each_char do |t|
					resolved_types << get_type(t, type64, declared_type)
				end
			end
			
			name = declared_type if name.nil?
		
			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 << "*" unless hash.nil?
			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 "#{get_identifier(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
	
	# Gets the identifier, if it's a D keyoword it
	# will return string appended with a _ otherwise the string
	def get_identifier (str)
		return is_keyword?(str) ? str + "_" : str
	end	
	
	# Returns true if the given string is a D(2) keyword
	def is_keyword? (str)		
		case str
			when "abstract"; return true
			when "alias"; return true
			when "align"; return true
			when "asm"; return true
			when "assert"; return true
			when "auto"; return true

			when "body"; return true
			when "bool"; return true
			when "break"; return true
			when "byte"; return true

			when "case"; return true
			when "cast"; return true
			when "catch"; return true
			when "cdouble"; return true
			when "cent"; return true
			when "cfloat"; return true
			when "char"; return true
			when "class"; return true
			when "const"; return true
			when "continue"; return true
			when "creal"; return true

			when "dchar"; return true
			when "debug"; return true
			when "default"; return true
			when "delegate"; return true
			when "delete"; return true
			when "deprecated"; return true
			when "do"; return true
			when "double"; return true

			when "else"; return true
			when "enum"; return true
			when "export"; return true
			when "extern"; return true

			when "false"; return true
			when "final"; return true
			when "finally"; return true
			when "float"; return true
			when "for"; return true
			when "foreach"; return true
			when "foreach_reverse"; return true
			when "function"; return true

			when "goto"; return true

			when "idouble"; return true
			when "if"; return true
			when "ifloat"; return true
			when "import"; return true
			when "in"; return true
			when "inout"; return true
			when "int"; return true
			when "interface"; return true
			when "invariant"; return true
			when "ireal"; return true
			when "is"; return true

			when "lazy"; return true
			when "long"; return true

			when "macro"; return true
			when "mixin"; return true
			when "module"; return true

			when "new"; return true
			when "nothrow"; return true
			when "null"; return true

			when "out"; return true
			when "override"; return true

			when "package"; return true
			when "pragma"; return true
			when "private"; return true
			when "protected"; return true
			when "public"; return true
			when "pure"; return true

			when "real"; return true
			when "ref"; return true
			when "return"; return true

			when "scope"; return true
			when "shared"; return true
			when "short"; return true
			when "static"; return true
			when "struct"; return true
			when "super"; return true
			when "switch"; return true
			when "synchronized"; return true

			when "template"; return true
			when "this"; return true
			when "throw"; return true
			when "true"; return true
			when "try"; return true
			when "typedef"; return true
			when "typeid"; return true
			when "typeof"; return true

			when "ubyte"; return true
			when "ucent"; return true
			when "uint"; return true
			when "ulong"; return true
			when "union"; return true
			when "unittest"; return true
			when "ushort"; return true

			when "version"; return true
			when "void"; return true
			when "volatile"; return true

			when "wchar"; return true
			when "while"; return true
			when "with"; return true
			else return false;
		end
	end
	
	def templates_for_class (clazz)		
		templates = @templates.find_all do |template, value|
			value[:class] == clazz
		end
		
		return templates
	end
	
	def interface_for_protocol (protocol)
		interface = @interfaces2.find do |interface|
			interface.name == protocol
		end
		
		return interface
	end	
	
	def get_framework_name (framework)
		i = framework.rindex(".framework")
		return framework if i.nil?
		x = framework.rindex("/", i)
		framework[x + 1 ... i]
	end
	
	def sub_framework? (framework)
		i = framework.index("framework")
		return false if i.nil?
		!framework.index("framework", i + 1).nil?
	end
	
	def get_parent_framework_name (framework)
		return get_framework_name(framework) unless sub_framework?(framework)
		i = framework.index(".framework")
		return framework if i.nil?
		str = framework[0 ... i]
		x = str.rindex("/")
		return str if x.nil?
		str[x + 1 .. -1]
	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