diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sciprts/dstepgen.rb	Sat Jan 31 17:22:44 2009 +0100
@@ -0,0 +1,1388 @@
+#!/usr/bin/env ruby
+
+# Copyright (c) 2008-2009, Jacob Carlborg. All rights reserved.
+# Copyright (c) 2006-2007, Apple Inc. All rights reserved.
+# Copyright (c) 2005-2006 FUJIMOTO Hisakuni
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+# 3.  Neither the name of Apple Inc. ("Apple") nor the names of
+#     its contributors may be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+ 
+require "rubygems"
+gem "builder", "~> 2.0"
+require "builder"
+require "tmpdir"
+require "optparse"
+include Builder
+
+$KCODE = "UTF8"
+
+# Extensions that adds support for member access syntax
+class Hash	
+	def type
+		result = self["type"]
+		return result unless result.nil?
+		self[:type]
+	end
+	
+	def type= (type)
+		self[:type] = type
+	end
+	
+	def id
+		result = self["id"]
+		return result unless result.nil?
+		self[:id]
+	end
+	
+	def id= (id)
+		self[:id] = id
+	end
+	
+	def methods
+		result = self["methods"]
+		return result unless result.nil?
+		self[:methods]
+	end
+	
+	def methods= (methods)
+		self[:methods] = methods
+	end
+	
+	def method_missing (method, *args)
+		self.class.instance_eval do
+			define_method(method) do |*args|
+				if args.length > 0
+					self[method[0 ... -1]] = args[0]
+					self[eval(":#{method}"[0 ... -1])] = args[0]
+				else
+					result = self[method]
+					return result unless result.nil?
+					self[eval(":#{method}")]
+				end
+			end
+		end
+		
+		if (method = method.id2name) =~ /=/
+			eval("self.#{method} (args.length < 2 ? args[0] : args)")
+		else
+			eval("self.#{method}")
+		end
+	end
+end
+
+# This Struct represents an Objective-C Framework
+Framework = Struct.new(:name, :headers) do
+	def initialize
+		self.headers = []
+		self.name = ""
+	end
+end
+
+# This Struct represents a C/Objective-C header
+HeaderFile = Struct.new(:name, :framework, :cftypes, :constants, :defines, :enums, :externs, :functions,
+			  			:imports, :inline_functions, :opaques, :path, :structs, :typedefs) do
+	def initialize
+		self.name = ""
+		self.cftypes = []
+		self.constants = []
+		self.defines = []
+		self.enums = []
+		self.externs = []
+		self.framework = ""
+		self.functions = []
+		self.imports = []
+		self.inline_functions = []
+		self.opaques = []
+		self.path = ""
+		self.structs = []
+		self.typedefs = []
+	end	
+end
+
+# This class scans the headers
+class HeaderScaner
+	CPP = ['/usr/bin/cpp-4.0', '/usr/bin/cpp-3.3', '/usr/bin/cpp3'].find { |x| File.exist?(x) }
+	raise "cpp not found" if CPP.nil?
+	CPPFLAGS = "-D__APPLE_CPP__ -include /usr/include/AvailabilityMacros.h"
+	CPPFLAGS << "-D__GNUC__" unless /\Acpp-4/.match(File.basename(CPP))
+	
+	attr_accessor :frameworks, :headers, :do_64bit
+	
+	def initialize
+		@extern_name = 'extern'
+		@frameworks = []
+		@file_content = nil
+		@headers = []
+		@classes = {}
+		@informal_protocols = {}
+		@function_pointer_types = {}
+		@do_64bit = false
+	end
+	
+	def classes
+		@classes
+	end
+	
+	def protocols
+		@informal_protocols
+	end
+	
+	def cftypes (header)
+		re = /typedef\s+(const\s+)?(struct\s*\w+\s*\*\s*)([^\s]+Ref)\s*;/
+		@cpp_result.scan(re).each do |m|
+			header.cftypes << { :name => m[2], :type => m[1], :const => m[0] =~ /const/ ? true : false}
+		end
+	end
+	
+	def constants (header)
+		tmp = header.externs.map do |i|
+			constant?(i, true)
+		end
+		
+		header.constants = tmp.flatten.compact
+	end
+	
+	def defines (header)
+		re = /#define\s+([^\s]+)\s+(\([^)]+\)|[^\s]+)\s*$/
+		@file_content.scan(re).each do |m|
+			next unless !m[0].include?('(') && m[1] != '\\'
+			header.defines <<  { :name => m[0], :value => m[1] }
+		end
+	end
+	
+	def enums (header)
+		re = /\benum\b\s*(\w+\s+)?\{([^}]*)\}/
+		@cpp_result.scan(re).each do |m|
+			enum = { :name => m[0], :members => [] }
+			
+			m[1].split(",").map do |i|
+				name, value = i.split("=", 2).map do |x|
+					x.strip
+				end
+				
+				enum[:members] << { :name => name, :value => value } unless name.empty? || name[0] == ?#
+			end
+			
+			header.enums << enum
+		end
+	end
+	
+	def function_pointer_types (header)
+		re = /typedef\s+([\w\s]+)\s*\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;/
+		data = @cpp_result.scan(re)
+		re = /typedef\s+([\w\s]+)\s*\(([^)]+)\)\s*;/
+		data |= @cpp_result.scan(re).map do |m|
+			ary = m[0].split(/(\w+)$/)
+			ary[1] << " *"
+			ary << m[1]
+			ary
+		end
+		
+		data.each do |m|
+			name = m[1]
+			args = m[2].split(",").map do |x|
+				if x.include?(" ")
+					ptr = x.sub!(/\[\]\s*$/, "")
+					x = x.sub(/\w+\s*$/, "").strip
+					ptr ? x + "*" : x
+				else
+					x.strip
+				end
+			end
+			
+			type = "#{m[0]}(*)(#{args.join(', ')})"
+			@function_pointer_types[name] = type
+		end
+	end
+	
+	def typedefs (header)
+		re = /^\s*typedef\s+(.+)\s+([\w\*]+)\s*;$/
+		data = @cpp_result
+		data.scan(re).each do |m|
+			var = get_var_info(m[0] + " " + m[1])
+			header.typedefs << get_var_info(m[0] + " " + m[1]) if var
+		end
+	end
+	
+	def externs (header)
+		re = /^\s*#{@extern_name}\s+\b(.*)\s*;.*$/
+		header.externs = @cpp_result.scan(re).map do |m|
+			m[0].strip
+		end
+	end
+	
+	def imports (header)
+		tmp = []
+			
+		@file_content.each do |line|
+			if line =~ /#(include|import) <(.+)\.h>/
+				next if $2 == header.name
+				tmp << $2
+			elsif line =~ /@class(\s+)(\w+)(,|;)/
+				next if $2 == header.name
+				
+				if $3 == ";"
+					tmp << header.framework + "/" + $2 unless header.framework == ""
+					tmp << $2 if header.framework == ""
+				elsif
+					str = line[6 + $1.length ... -2]
+					str.gsub!(" ", "")
+					arr = str.split(",")
+					
+					arr.each do |s|
+						tmp << header.framework + "/" + s unless header.framework == ""
+						tmp << s if header.framework == ""
+					end
+				end
+			end
+		end
+		
+		header.imports = tmp.compact.uniq
+	end
+	
+	def informal_protocols (header)
+		self.methods(header)
+	end	
+	
+	def methods (header)
+		interface_re = /^@(interface|protocol)\s+(\w+)\s*:?\s*(\w*)\s*(\([^)]+\))?/
+		
+		end_re = /^@end/
+		body_re = /^[-+]\s*(\([^)]+\))?\s*([^:\s;]+)/
+		args_re = /\w+\s*:/
+		prop_re = /^@property\s*(\([^)]+\))?\s*([^;]+);$/
+		current_interface = current_category = nil
+		i = 0
+		parent = nil	
+		
+		@cpp_result.each_line do |line|
+			size = line.size
+			line.strip!
+			
+			if md = interface_re.match(line)
+				parent = nil
+				current_interface = md[1] == "protocol" ? "NSObject" : md[2]
+				current_category = md[4].delete("()").strip if md[4]
+				parent = md[3] unless md[3] == ""
+				
+			elsif end_re.match(line)
+				current_interface = current_category = nil
+				
+			elsif current_interface && md = prop_re.match(line)
+				# Parsing Objective-C 2.0 properties
+				if (a = md[2].split(/\s/)).length >= 2 && /^\w+$/.match(name = a[-1]) && (type = a[0 .. -2].join(" ")).index(",").nil?
+					getter, setter = name, "set#{name[0].chr.upcase + name[1 .. -1]}"
+					readonly = false
+					
+					if attributes = md[1]
+						if md = /getter\s*=\s*(\w+)/.match(attributes)
+							getter = md[1]
+						end
+						
+						if md = /setter\s*=\s*(\w+)/.match(attributes)
+							setter = md[1]
+						end
+						
+						readonly = true if attributes.index("readonly")
+					end
+					
+					typeinfo = VarInfo.new(type, "", "")
+					
+					@classes[current_interface] ||= {}					
+					methods = (@classes[current_interface].methods ||= [])
+					methods << MethodInfo.new(typeinfo, getter, false, [], line)
+					
+					unless readonly
+						methods << MethodInfo.new(VarInfo.new("void", "", ""), setter + ":", false, [typeinfo], line)
+					end
+				end
+				
+			elsif current_interface && (line[0] == ?+ || line[0] == ?-)
+				mtype = line[0]
+				data = @cpp_result[i .. -1]
+				body_md = body_re.match(data)
+				
+				next if body_md.nil?
+				
+				rettype = body_md[1] ? body_md[1].delete("()") : "id"
+				retval = VarInfo.new(rettype, "", "")
+				args = []
+				selector = ""
+				data = data[0 .. data.index(";")]
+				args_data = []
+				
+				data.scan(args_re) do |x|
+					args_data << [$`, x, $']
+				end
+				
+				variadic = false
+				args_data.each_with_index do |ary, n|
+					before, argname, argtype = ary
+					arg_nameless = (n > 0 && /\)\s*$/.match(before))
+					argname = ":" if arg_nameless
+					realargname = nil
+					
+					if n < args_data.length - 1
+						argtype.sub!(args_data[n + 1][2], "")
+						
+						if arg_nameless
+							argtype.sub!(/(\w+\s*)?\w+\s*:\s*$/, "")
+						else
+							unless argtype.sub!(/(\w+)\s+\w+:\s*$/)	{ |s| realargname = $1; "" }
+								# maybe the next argument is nameless
+								argtype.sub!(/\w+\s*:\s*$/, "")
+							end
+						end
+					else
+						argtype.sub!(/\s+__attribute__\(\(.+\)\)/, "")
+						
+						if arg_nameless
+							argtype.sub!(/\w+\s*;$/, "")
+						else
+							unless argtype.sub!(/(\w+)\s*;$/) { |s| realargname = $1; "" }
+								variadic = argtype.sub!(/,\s*\.\.\.\s*;/, "") != nil
+								argtype.sub!(/\w+\s*$/, "") if variadic
+							end
+						end
+					end
+					
+					selector << argname
+					realargname ||= argname.sub(/:/, "")
+					args << VarInfo.new(argtype, realargname, "") unless argtype.empty?
+				end
+				
+				selector = body_md[2] if selector.empty?
+				args << VarInfo.new("...", "vararg", "") if variadic
+				method = MethodInfo.new(retval, selector, line[0] == ?+, args, data)
+				
+				if current_category && current_interface == "NSObject"
+					(@informal_protocols[current_category] ||= []) << method
+				end
+				
+				@classes[current_interface] ||= {}
+				
+				if header.name == current_interface				
+					@classes[current_interface].file = header.name
+				else
+					@classes[current_interface].file ||= header.name
+				end
+				
+				unless parent == current_interface || parent =~ /\s+/ || parent.nil?
+					@classes[current_interface].parent = parent
+				end				
+				
+				(@classes[current_interface].methods ||= []) << method
+			end
+			i += size
+		end
+	end	
+	
+	def structs (header)
+		re = /typedef\s+struct\s*\w*\s*((\w+)|\{([^{}]*(\{[^}]+\})?)*\}\s*([^\s]+))\s*(__attribute__\(.+\))?\s*;/
+		i = 0
+		body = nil
+		@cpp_result.scan(re).each do |m|
+			struct = { :name => m[4], :members => [] }
+			
+			unless struct[:name].nil?
+				if struct[:name][0, 1] == "*"
+					struct[:name].sub!("*", "")
+				end
+			end
+			
+			return_type = nil
+			stripped_return_type = nil
+			body = m[2]
+			
+			if m[2]
+				m[2].split(/,|;/).map do |i|				
+					str, bytes = i.split(":", 2).map do |x|
+						x.strip
+					end
+	
+					var = get_var_info(str, true)
+	
+					if var				
+						if var.return_type == "***dummy***"
+							var.return_type = return_type
+							var.stripped_return_type = stripped_return_type
+						else
+							return_type = var.return_type
+							stripped_return_type = var.stripped_return_type
+						end
+	
+						struct[:members] << { :name => var.name, :bytes => bytes, :declaredType => var.return_type, :type => "", :type64 => "" } unless str.empty? || str[0] == ?#
+						
+						names = []
+						
+						tmp = struct[:members].collect do |member|
+							unless names.include?(member[:name])
+								names << member[:name]
+								member
+							end
+						end
+						
+						struct[:members] = tmp.compact
+					end
+				end
+			end
+			
+			header.structs << struct if body
+		end
+	end
+	
+	def functions (header, inline = false)
+		if inline
+			inline_func_re = /(inline|__inline__)\s+((__attribute__\(\([^)]*\)\)\s+)?([\w\s\*<>]+)\s*\(([^)]*)\)\s*)\{/
+			res = @cpp_result.scan(inline_func_re)
+			res.each do |x|
+				x.delete_at(0)
+				x.delete_at(1)
+			end
+		else
+			skip_inline_re = /(static)?\s(inline|__inline__)[^{;]+(;|\{([^{}]*(\{[^}]+\})?)*\})\s*/
+			func_re = /(^([\w\s\*<>]+)\s*\(([^)]*)\)\s*)(__attribute__[^;]+)?;/
+			res = @cpp_result.gsub(skip_inline_re, '').scan(func_re)
+		end
+		
+		funcs = res.map do |m|
+			orig, base, args = m			
+			base.sub!(/^.*extern\s/, "")
+			func = constant?(base)
+			
+			if func
+				args = args.strip.split(",").map do |i|
+					constant?(i)
+				end
+				
+				next if args.any? do |x|
+					x.nil?
+				end
+				
+				args = [] if args.size == 1 && args[0].return_type == "void"	
+				FunctionInfo.new(func, args, orig, inline)
+			end
+		end.compact
+		
+		if inline
+			header.inline_functions = funcs
+		else
+			header.functions = funcs
+		end
+	end
+	
+	def prepare (path)		
+		@file_content = File.read(path)
+		@file_content.gsub!(%r{(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)}, "");
+		@complete_cpp_result, @cpp_result = do_cpp(path, false, true, "")
+	end	
+	
+	def scan (frameworks, headers)
+		@frameworks = frameworks
+		@headers = headers
+		
+		@frameworks.each do |framework|			
+			framework.headers.each do |header|
+				prepare(header.path)
+				
+				imports(header)
+				cftypes(header)
+				externs(header)
+				constants(header)
+				enums(header)
+				structs(header)
+				typedefs(header)
+				functions(header)
+				functions(header, true)
+				defines(header)
+				methods(header)
+				function_pointer_types(header)
+			end
+		end
+		
+		@headers.each do |header|
+			prepare(header.path)
+			
+			imports(header)
+			cftypes(header)
+			externs(header)
+			constants(header)
+			enums(header)
+			structs(header)
+			typedefs(header)
+			functions(header)
+			functions(header, true)
+			defines(header)
+			methods(header)
+			function_pointer_types(header)
+		end
+	end
+	
+	def get_var_info (str, multi = false)
+		str.strip!
+		
+		return nil if str.empty?
+		
+		if str == "..."
+			VarInfo.new("...", "...", str)
+		else
+			str = "***dummy*** " + str if str[-1].chr == '*' || str.index(/\s/).nil?	
+			tokens = multi ? str.split(',') : [str]
+			part = tokens.first
+			re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/
+			m = re.match(part)	
+	
+			if m
+				return nil if m[1].split(/\s+/).any? do |x|
+					['end', 'typedef'].include?(x)
+				end
+				
+				m = m.to_a[1..-1].compact.map do |i|
+					i.strip
+				end
+				
+				m[0] += m[2] if m.size == 3
+				m[0] = 'void' if m[1] == 'void'
+				
+				var = begin
+					VarInfo.new(m[0], m[1], part)
+				rescue
+					return nil
+				end
+				
+				if tokens.size > 1
+					[var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }]
+				else
+					var
+				end				
+			end
+		end
+	end
+	
+	def constant? (str, multi = false)
+		str.strip!
+		return nil if str.empty?
+		
+		if str == '...'
+			VarInfo.new('...', '...', str)
+		else
+			str << " dummy" if str[-1].chr == '*' or str.index(/\s/).nil?
+			tokens = multi ? str.split(',') : [str]
+			part = tokens.first
+			re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/
+			m = re.match(part)
+			
+			if m
+				return nil if m[1].split(/\s+/).any? do |x|
+					['end', 'typedef'].include?(x)
+				end
+				
+				m = m.to_a[1..-1].compact.map do |i|
+					i.strip
+				end
+				
+				m[0] += m[2] if m.size == 3
+				m[0] = 'void' if m[1] == 'void'
+				
+				var = begin
+					VarInfo.new(m[0], m[1], part)
+				rescue
+					return nil
+				end
+				
+				if tokens.size > 1
+					[var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }]
+				else
+					var
+				end
+			end
+		end
+	end	
+	
+	class VarInfo
+		
+		attr_reader :name, :orig, :const, :type_modifier
+		attr_accessor :octype, :resolved_type, :resolved_type64, :return_type, :stripped_return_type
+		
+		def initialize (type, name, orig)
+			@return_type = type.clone
+			@name = name
+			@orig = orig
+			@const = false
+			@type_modifier = ""
+			
+			@return_type.gsub!(/\[[^\]]*\]/, "*")
+			
+			if @return_type =~ /\bconst\b/
+				@const = true
+				@return_type.gsub!("const", "")
+			end
+			
+			if @return_type =~ /\b(in|out|inout|oneway|bycopy|byref)\b/
+				
+				case $1
+				when "in"
+					@type_modifier << "n"
+				when "out"
+					@type_modifier << "o"
+				when "inout"
+					@type_modifier << "N"
+				when "oneway"
+					@type_modifier << "w"
+				when "bycopy"
+					@type_modifier << "c"
+				when "byref"
+					@type_modifier << "r"
+				end
+				
+				@return_type.gsub!("#{$1}", "")
+			end
+			
+			@return_type.gsub!(/\s?\*\s?/, "*")
+			@return_type.gsub!(/^\(|\)$/, "")
+			@return_type.strip!
+			
+			t = type.gsub(/\b(__)?const\b/, "")
+			t.gsub!(/<[^>]*>/, '')
+			t.gsub!(/\b(in|out|inout|oneway|const)\b/, "")
+			t.gsub!(/\b__private_extern__\b/, "")
+			t.gsub!(/^\s*\(?\s*/, "")
+			t.gsub!(/\s*\)?\s*$/, "")
+			
+			raise "Empty type (was '#{type}')" if t.empty?
+			
+			@stripped_return_type = t
+		end
+		
+		def function_pointer? (function_pointer_types)
+			type = @function_pointer_types[@stripped_return_type] || @stripped_return_type
+			@function_pointer_type ||= FunctionPointerInfo.new_from_type(type)
+		end
+		
+		def <=>(x)
+			self.name <=> x.name
+		end					
+		
+		def hash
+			@name.hash
+		end
+		
+		def eql? (o)
+			@name == o.name
+		end
+	end
+ 
+	class FunctionInfo < VarInfo
+		
+		attr_reader :args, :argc
+		
+		def initialize (function, args, orig, inline = false)
+			super(function.return_type, function.name, orig)
+			
+			@args = args
+			@argc = @args.size
+			@variadic = false
+			
+			if @args[-1] && @args[-1].return_type == "..."
+				@argc -= 1
+				@variadic = true
+				@args.pop
+			end
+			
+			@inline = inline
+			self
+		end
+		
+		def variadic?
+			@variadic
+		end
+		
+		def inline?
+			@inline
+		end
+	end
+	
+	class FunctionPointerInfo < FunctionInfo
+		def initialize (return_type, arg_types, orig)
+			args = arg_types.map do |x|
+				VarInfo.new(x, "", "")
+			end
+			
+			super(VarInfo.new(return_type, "", ""), args, orig)
+		end
+		
+		def self.new_from_type (type)
+			@cache ||= {}
+			info = @cache[type]
+			
+			return info if info
+			
+			tokens = type.split(/\(\*\)/)
+			return nil if tokens.size != 2
+			
+			return_type = tokens.first.strip
+			rest = tokens.last.sub(/^\s*\(\s*/, "").sub(/\s*\)\s*$/, "")
+			
+			arg_types = rest.split(/,/).map do |x|
+				x.strip
+			end
+			
+			@cache[type] = self.new(return_type, arg_types, type)
+		end
+	end
+	
+	class MethodInfo < FunctionInfo
+		
+		attr_reader :selector
+		
+		def initialize (method, selector, is_class, args, orig)
+			super(method, args, orig)
+			
+			@selector = selector
+			@is_class = is_class
+			self
+		end
+		
+		def class_method?
+			@is_class
+		end
+		
+		def <=>(o)
+			@selector <=> o.selector
+		end
+		
+		def hash
+			@selector.hash
+		end
+		
+		def eql? (o)
+			@selector == o.selector
+		end
+	end
+	
+	def do_cpp (path, fails_on_error = true, do_64 = true, flags = "")
+	    f_on = false
+	    err_file = '/tmp/.cpp.err'
+	    cpp_line = "#{CPP} #{CPPFLAGS} #{flags} #{do_64 ? '-D__LP64__' : ''} \"#{path}\" 2>#{err_file}"
+	    complete_result = `#{cpp_line}`
+		
+		if $?.to_i != 0 && fails_on_error
+			$stderr.puts File.read(err_file)
+			File.unlink(err_file)
+			raise "#{CPP} returned #{$?.to_int/256} exit status\nline was: #{cpp_line}"
+		end
+		
+		result = complete_result.select do |s|
+			# First pass to only grab non-empty lines and the pre-processed lines
+			# only from the target header (and not the entire pre-processing result).
+			next if s.strip.empty?
+			m = %r{^#\s*\d+\s+"([^"]+)"}.match(s)
+			f_on = (File.basename(m[1]) == File.basename(path)) if m
+			f_on
+		end.select do |s|
+			# Second pass to ignore all pro-processor comments that were left.
+			/^#/.match(s) == nil
+		end.join
+		
+		File.unlink(err_file)
+		return [complete_result, result]
+	end
+end
+
+class DStepGenerator
+	
+	VERSION = 1.0
+	
+	attr_accessor :out_file, :scaner
+	
+	def initialize
+		@do_64bit = false
+		@frameworks = []
+		@framework_paths = []
+		@headers = []
+		@import_directives = ""
+		@informal_protocols = []
+		@classes = []
+		@scaner = HeaderScaner.new
+		@scaner.do_64bit = @do_64bit
+	end
+	
+	def do_64bit
+		return @do_64bit
+	end
+	
+	def do_64bit= (do_64bit)
+		@do_64bit = do_64bit
+		@scaner.do_64bit = do_64bit
+	end	
+	
+	def collect		
+		scaner.scan(@frameworks, @headers)
+		@classes = scaner.classes
+		@informal_protocols = scaner.protocols
+	end
+	
+	def handle_framework (framework)
+		val = framework.name
+		path = framework_path(val)
+		
+		raise "Can't locate framework '#{val}'" if path.nil?
+		@framework_paths << File.dirname(path)
+		raise "Can't find framework '#{val}'" if path.nil?
+		
+		parent_path, name = path.scan(/^(.+)\/(\w+)\.framework\/?$/)[0]
+		
+		if @private
+			headers_path = File.join(path, "PrivateHeaders")
+			raise "Can't locate private framework headers at '#{headers_path}'" unless File.exist?(headers_path)
+			
+			headers = Dir.glob(File.join(headers_path, "**", "*.h"))
+			public_headers_path = File.join(path, "Headers")
+			public_headers = if File.exist?(public_headers_path)
+				HeaderScaner::CPPFLAGS << " -I#{public_headers_path} "
+				Dir.glob(File.join8(headers_path, "**", "*.h"))
+			else
+				[]
+			end
+		else
+			headers_path = File.join(path, "Headers")
+			raise "Can't locate public framework headers at '#{headers_path}'" unless File.exist?(headers_path)
+			public_headers = headers = Dir.glob(File.join(headers_path, "**", "*.h"))
+		end
+		
+		# We can't just "#import <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
\ No newline at end of file