=begin
	MetaRuby
	Copyright 2001 by Mathieu Bouchard

	Type.rb: Type System (related to RubySchema, RubyX11)

	$Id: Type.rb,v 1.3 2001/09/18 03:02:26 matju Exp $

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
=end

require "Contract"

#----------------------------------------------------------------#

# this is not a metatype.
unless Object.const_defined? :Boolean
	module Boolean; end
	[TrueClass,FalseClass].each{|x| x.instance_eval { include Boolean }}
end

def self.add(sym,object)
	const_set(sym,object)
	if object.respond_to? :const_name_is then
		object.const_name_is(sym,self)
	end
end

#----------------------------------------------------------------#

=begin module Type

	a Type object represents a set of potential and/or existing objects
	that share common characteristics. This is a more general concept than
	module or class, in that the thing that is of a certain type need not
	even know about it (so you can have a type for arbitrary subsets eg:
	odd Integers)

	include Type

		this declares a class as a metatype. Instances of it are types.

	extend Type

		this declares an object as a type.

	#===(object) ^Boolean

		The type-checking method.
		Is set inclusion predicate just like Module#===.
=end

module Type
	module Contract
		def ===(object)
			result = super
			assert_type("post: result",result,Boolean)
			result
		end
	end
end

class Module; include Type; end
class Class; include Type; end

=begin module Nameable
=end

module Nameable
	attr_accessor :const_name
	def const_name_is(sym,holder); @const_name = "#{holder}::#{sym}"; end
	def to_s;    @const_name || super; end
	def inspect; @const_name || super; end
#	def dup; r=super; r.instance_eval{@const_name=nil}; r; end
end

=begin module Template
	hmmm, revise this...
=end

module Template
	def of(*args)
		(@specializations ||= {})[args] ||= instantiate_template(*args)
	end
end

=begin class ChoiceType
=end

# ChoiceTypes represents a set of symbols,strings,etc. that form a type.
class ChoiceType; include Type, Nameable
	attr_reader :index_to_val, :val_to_index
	def initialize(*args)
		@index_to_val = args
		@val_to_index = {}
		@index_to_val.each_with_index {|v,i| @val_to_index[v] = i }
		[@index_to_val,@val_to_index].each{|x|x.freeze}
	end
	def []    (v); @index_to_val[v]; end
	def lookup(v); @val_to_index[v]; end
	def ===(v); !! lookup(v); end
	class<<self; alias [] new; end
end

class Any; include Type, Nameable, Contract; extend Template
	class<<self
		alias instantiate_template new
	end
	def initialize(*alternatives)
#		p alternatives
		alternatives.each{|a| assert_type("a",a,Type) }
		@alternatives = alternatives.freeze
	end
	def ===(o)
		!! @alternatives.find {|t| t===o }
	end
	def to_s
		if @const_name then
			super
		else
			Any.to_s + ".of(" + @alternatives.join(",") + ")"
		end
	end
	alias inspect to_s
end

class List; include Type, Nameable, Contract; extend Template
	class<<self
		alias instantiate_template new
	end
	def initialize(element_type)
		@element_type = element_type
	end
	def ===(o)
		return false unless Array===o
		o.each {|e|
			return false unless @element_type===e
		}
		true
	end
end

# subclasses must define #tuple.
class TupleBase
	class<<self
		alias [] new
		attr_reader :fields
	end
	def method_missing(sym,*args)
		if type.compile_fields then send(sym) else super end
	end

	def to_s; "#{type}[#{tuple.join ', '}]"; end
	def inspect; "#{type}#{tuple.inspect}"; end
end

class Tuple < TupleBase; extend Type
# category: class methods

	def self.fields_are(*fs)
		raise "Redefinition of a Tuple class' fields" if @fields
		@fields = fs.map {|sym,type,opt| TupleField[sym,type,opt] }
		compile_fields
	end

	def self.compile_fields
		return false if @compiled_fields
		e=""
		fields.each_with_index {|f,i| e << "def #{f.sym};@tuple[#{i}]end;" }
		class_eval e
		@compiled_fields = true
	end

	def self.check_types(*args)
		fields.length == args.length or raise ArgumentError,
			"wrong number of args: got #{args.length} "\
			"expecting #{fields.length} "\
			"while constructing a #{self}"
		fields.each_with_index {|f,i|
			if not f.vtype === args[i] then
				raise TypeError, "for an #{self}, "\
					"supplied #{f.sym}=#{args[i].inspect} "\
					"is not in type #{f.vtype.inspect}"
			end
		}
		args
	end

	#!@#$ are those really useful?
	def self.field_names; fields.map{|f| f.sym   }; end
	def self.field_types; fields.map{|f| f.vtype }; end

# category: instance methods

	def initialize(*args)
		if args.length==1 and Hash===args[0] then
			h = args[0]
			f=nil
			args=[]
			for f in type.fields; args << h[f.sym]; end
		end
		@tuple = args
		p args
		type.check_types(*args)
	end

	attr_reader :tuple # Array

	def [](i); @tuple[i]; end
	def hash; tuple.hash; end
	def to_s; "#{type}[#{@tuple.join ', '}]"; end
	def inspect; "#{type}#{@tuple.inspect}"; end
	def to_a  ; tuple.dup; end
	def to_ary; tuple.dup; end

	def each
		type.fields.each_with_index {|f,i| yield f.sym, @tuple[i] }
	end

	def to_h
		foo={}
		type.fields.each_with_index {|(sym,vtype),i|
			foo[sym] = tuple[i]
		}
		foo
	end

	def ==(other); type == other.type && tuple == other.tuple; end
	alias eql? ==
end

# this tuple is not defined by the same means than the others;
# this is because of a chicken-and-egg issue.
#!@#$ really? something can be done here.
class TupleField < Tuple
	class<<self; alias [] new; end
	def sym    ; @tuple[0]; end
	def vtype  ; @tuple[1]; end
	def options; @tuple[2]; end
	def initialize(sym,vtype,options=nil)
		@tuple = sym,vtype,options
	end
	@fields = [
		TupleField[:sym,Symbol],
		TupleField[:vtype,Type],
		TupleField[:options,NilClass],
	]
end
