view sciprts/dstepgen.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 source

#!/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 <x/x.h>" 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 <stdio.h>\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] <headers...>"
		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