#!/usr/bin/env ruby
# $Id: Hash.rb,v 1.6 2001/08/28 09:07:49 matju Exp $
=begin

	MetaRuby
	file: Hollow Hash

	Copyright (c) 2001 by Mathieu Bouchard
	Licensed under the same license as Ruby.

=end

require "Contract"

module HashP; end
class Hash; include HashP; end

#--------------------------------------------------------------------------#
module SimpleHashP; module Contract
	include ::Contract

	# a hash consists of a keyset for which membership can be tested very
	# quickly and for which exactly one value is associated to each key.

	def length
		result = super
		assert_type("post: result",result,Fixnum)
		assert_nonneg("post: result",result)
		result
	end

	# returns the size of the keyset of the table

	def has_key?(k)
		result = super
		assert_type("post: result",result,Boolean)
		result
	end

	def get(k)
		assert_key(k)
		super
	end

	def put(k,v)
		assert_not_frozen
		super
	end

	def remove(k)
		assert_not_frozen
		assert_key(k)
		result = super
		assert_type("post: result",result,NilClass)
		result
	end

	# has_key? tests for presence of k in the keyset. returns a Boolean.
	# if k is in the keyset, get can be called to get the associated value.
	# put:
		# adds k to the keyset if not already present
		# associates v with k (overriding any previous associated value)
		# returns void
		# nil is *not* a special case for v
	# remove:
		# removes k from the keyset
		# returns void

	def each_key(&proc)
		assert_type("block",proc,Proc)
		super
	end

	# for every k in the keyset, proc.call(k)
	# returns self

	# #rehash rebuilds the hash table. If the hash table cannot support
	# running rehash inside #each-like methods, IndexError should be raised.
	# returns nil
	def rehash
		super
	end		

	# note:
	# #dup() is no longer in SimpleHashP.
	# override by a raise TypeError if #dup() must be forbidden.

	# Exceptions:
	# ArgumentError: misc argument error, if applicable
	# May throw other exceptions when it makes sense.


# category: assertions

	def assert_key(k)
		if not has_key?(k) then raise ArgumentError, "key must exist"; end
	end
end end

#--------------------------------------------------------------------------#
# emulation of Hash using an implementation of SimpleHashP

module HollowHash
	include HashP
	include Enumerable

# category: base

	def initialize(ifnone=nil)
		@frozen = false # not used yet
		self.default = ifnone
	end

# category: instance variables
	
	attr_accessor :default

# category: frozen

	def modify(&proc)
		frozen? and raise TypeError, "can't modify frozen array"
		proc.call
	end
	
	def freeze; @frozen = true; end
	def frozen?; @frozen; end

# category: [] and []=

	def get_default(k); default; end

	def [](k)
		fetch(k) { get_default(k) }
	end

	def []=(k,v); modify {
		put(k,v)
		get(k)
	}end

	#!@#$ the behaviour of *default vs &proc is not right
	def fetch(k,*default,&proc)
		#!@#$ if default.length > 1 then ... error ...
		if has_key?(k) then
			get(k)
		elsif proc then
			proc.call(k)
		elsif default.length == 1 then
			default[0]
		else
			raise IndexError, "key not found"
		end
	end

	# do not use alias here
	def store(k,v)
		self[k]=v
	end

	def delete(k,&proc); modify {
		if has_key?(k) then
			v = get(k)
			remove(k)
			v
		else
			proc ? proc.call(k) : nil
		end
	}end

# category: miscellaneous operations

	def ==(other)
		return false unless HashP === other
		return false unless length == other.length
		each {|k,v|
			return false if other[k] != v
		}
		true
	end

	def clear; modify {
		each_key {|k| delete k }
	}end

	def delete_if(&proc); modify {
		each {|k,v|
			delete k if proc.call k,v
		}
	}end

	# same as HollowArray's
	def reject!(&proc); modify {
		foo = length
		delete_if(&proc)
		foo != length ? self : nil
	}end

	def each(&proc)
		each_key {|k|
			v = fetch k
			proc.call k,v
		}
	end
	alias each_pair each

	def each_value(&proc)
		each {|k,v| proc.call v }
	end

	# same as HollowList's
	def empty?
		length == 0
	end

	#!@#$ there's a big problem with each_value and return (?)
	def has_value?(v)
		result = false
		each_value {|v2|
			if v == v2 then result = true; break; end
		}
		result
	end
	alias value? has_value?

	#!@#$ there is a big problem with each and return (?)
	def index(v)
		result = nil
		each {|k,v2|
			if v == v2 then result = k; break; end
		}
		result
	end

	def indices(*keys)
		keys.collect {|k| has_key?(k) ? get(k) : get_default(k) }
	end
	# same as HollowArray's
	alias indexes indices

	# do not use alias here.
	def key?    (k) has_key?(k); end
	def include?(k) has_key?(k); end
	def member? (k) has_key?(k); end

	def keys
		foo = []
		each_key {|k| foo << k }
		foo
	end

	# do not use alias here.
	def size; length; end

	def invert
		foo = type.new
		each {|k,v| foo[v] = k }
		foo
	end

	def shift; modify {
		k=v=nil
		each_pair {|k,v| break }
		delete k
		[k,v]
	}end

	def values
		foo = []
		each_value {|v| foo << v }
		foo
	end

	def to_a
		foo = []
		each_pair {|k,v| foo << [k,v] }
		foo
	end

	def to_s; to_a.to_s; end

	def replace(other); modify { clear; update(other); self }end

	def update(other); modify {
		other.each_pair {|k,v| self[k]=v }
		self
	}end

	def inspect
		"{%s}" % [ map {|k,v| "%s=>%s" % [k.inspect,v.inspect]}.join ', ' ]
	end

	def hash; id; end #?

	def to_hash
		foo = {}
		each_pair {|k,v| foo[k]=v }
		foo
	end

	def sort(&proc); to_a.sort(&proc); end

	def dup
		foo = type.new.replace(self)
		foo.taint if tainted?
		foo
	end

# category: dup wrappers for some methods

	# same as HollowArray's
	def reject(&proc); modify { dup.delete_if(&proc) }end

end

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

class AutoHash
	def self.[](*v); new.replace Hash[*v]; end

	def initialize(*args)
		@inner = {}
		super(*args)
	end

	module Implementation; include SimpleHashP
		def length; @inner.length; end
		def has_key?(k); @inner.has_key?(k); end
		def get(k); @inner[k]; end
		def put(k,value); @inner[k]=value; nil; end
		def remove(k); @inner.delete(k); nil; end
		def each_key(&proc); @inner.each_key {|x| proc.call x }; self end
		def rehash; @inner.rehash; end		
	end

	include HollowHash
	include Implementation
	include SimpleHashP::Contract
end
#--------------------------------------------------------------------------#

