#!/usr/bin/env ruby
# $Id: List.rb,v 1.3 2001/06/13 12:24:20 matju Exp $
=begin

	MetaRuby
	file: Hollow List (shared by Hollow Array, Hollow String)

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

=end

#--------------------------------------------------------------------------#
# an implementation of the things common to Array and String.
# warning: this module shouldn't be used directly.
module HollowList

# category: protocol that is provided by array and string modules

	# returns the builtin class that this mixin emulates
	def builtin_class;    raise NotImplementedError; end

	# returns an instance of that builtin type
	def builtin_new;      raise NotImplementedError; end

	# returns a v converted to the corresponding builtin type (like to_a)
	def to_builtin(v);    raise NotImplementedError; end

	# returns a v converted to the corresponding builtin type (like to_ary)
	def to_builtin2(v);   raise NotImplementedError; end

	# does object v implements either Hollow#{builtin} or #{builtin} ?
	def same_builtin?(v); raise NotImplementedError; end

	# this is called with an out-of-bounds index to get the default value.
	# it is used in expanding array and giving values to out-of-bounds cells.
	# generally implemented as just 'nil'.
	### this could eventually exist for strings in a way that removes some of
	### the cruft from HollowString module while still passing the tests (?)
	def get_default(i);   raise NotImplementedError; end

# category: frozen

	# the method that should wrap all calls to put and put_seq
	# for proper treatment of #frozen?. (maybe)
	def modify(&proc)
#		@enter||=[]; name = caller[0][/`.*'/][1..-2]
#		printf "%*senter: %s\n", 2*@enter.length, "", name; @enter<<name
		frozen? and raise TypeError, "can't modify frozen array"
		# sort sync check would go here
		# taint check should go here
		# tainted? and SAFE_LEVEL>=4 and raise SecurityError, "can't modify array"
#		begin
			proc.call
#		ensure
#			@enter.pop; printf "%*sleave: %s\n", 2*@enter.length, "", name
#		end
	end

# category: [] and []=

	def length=(n)
		d = n - length
		if d>0
			put_seq(length,0,(length...length+n).map{|x| get_default(x) })
		end
		if d<0
			put_seq(n,length-n,[])
		end
	end

	# private :length=

	# ruby 1.6
	def at(i)
		i += length if i < 0
		if (0...length) === i then get(i) else get_default(i) end
	end

	def at_range(i)
		return at_with_length(
			i.begin,
			i.end - i.begin + (i.exclude_end? ? 0 : 1))
	end

	def at_with_length(i,n)
		i += length if i < 0
		return nil unless (0..length) === i && n >= 0
		if i+n > length then n = length - i end
		return builtin_new if n <= 0
		return get_seq(i,n)
	end

	def [](i,*args)
		if Range === i then
			if args.length > 0 then
				raise ArgumentError,
					"wrong # of arguments (%d for 1)" % [
						args.length + 1]
			end
			return at_range(i)
		end
		if args.length == 0 then return at(i) end
		if args.length > 1 then
			raise ArgumentError,
				"wrong # of arguments (%d for 2)" % args.length
		end
		return at_with_length(i,args[0])
	end

	def at_put(i,v)
		if i < 0 then i += length end
		if i < 0 then
			raise IndexError, "index %d out of string or array" % i
#		elsif HollowString===self and not Fixnum===v then
#			v=convert_type(v,Fixnum,:to_i)
		else
			self.length = i+1 if i >= length
			raise "i=%d, length=%d" %[i,length] if i>=length
			ol=length
			put i,v
			raise "i=%d, length=%d,ol=%d" %[i,length,ol] if i>=length
			return get i
		end
	end

	def at_range_put(i,v)
		begin
			# it's beginning to get redundant and patchy
			a = i.begin; a+=length if a<0
			b = i.end  ; b+=length if b<0
			at_with_length_put(a, b-a+(i.exclude_end? ? 0 : 1), v)
			#at_with_length_put(
			#	i.begin,
			#	i.end - i.begin + (i.exclude_end? ? 0 : 1),
			#	v)
		rescue IndexError => e
			raise RangeError, e.message
		end
	end

	def at_with_length_put(i,n,v)
		i += length if i < 0
		return nil if n < 0
		# the list is grown differently than with a single element.
		self.length = i if i>length
		n=length-i if i+n>length
		put_seq i,n,to_builtin(v)
		v
	end

	def []=(i,b,*args)
		if Range === i then
			if args.length > 0 then
				raise ArgumentError,
					"wrong # of arguments (%d for 2)" % [
						args.length + 2]
			end
			return at_range_put(i,b)
		end
		if args.length == 0 then
			return at_put(i,b)
		end
		if args.length > 1 then
			raise ArgumentError,
				"wrong # of arguments (%d for 3)" % [
					args.length+2]
		end
		at_with_length_put(i,b,args[0])
	end

	# private :length=
	# private :at_range, :at_with_length
	# private :at_put, :at_range_put, :at_with_length_put

	def slice!(i,*a)
		if a.length == 0 and Integer===i then
			(-length...length)===i or return get_default(i)
			result = self[i]
			self[i,1] = builtin_new
			result
		else
			result = self[i,*a]
			if Integer===i or Range===i then
				result or raise Integer===i ? IndexError : RangeError,
						"#{i} out of range"
			end
			self[i,*a] = builtin_new
			result
		end
	end

# category: predicates and such

	def <=>(other)
		c = 0
		n = [length,other.length].min
		n.times {|i|
			a = get(i)
			b = other[i]
			c = a <=> b
			break if c != 0
		}
		return (if c==0 then length <=> other.length else c end)
	end

	def eql?(other)
		return false unless same_builtin?(other)
		return false unless length == other.length
		length.times {|i|
			a = get(i)
			b = other[i]
			return false unless a.id==b.id || a.eql?(b)
		}
		true
	end

	#!@#$ should ==/eq handle loops other than selfloops? (when array)

	# eq is array-style equality.
	# == is same as eq by default, but in string, *may* be case-insensitive.
	def ==(other); eq(other); end
	def eq(other)
		return false unless same_builtin?(other)
		return false unless length == other.length
		# can't use (self <=> other) == 0
		length.times {|i|
			a = get(i)
			b = other[i]
			return false unless a.id==b.id || a==b
		}
		true
	end

	# private :eq

# category: stack/queue

#	def <<(v)
#		put_seq length,0,[v.to_s]
#		self
#	end

# category: misc

	def *(n)
		n >= 0 or raise ArgumentError, "negative count"
		foo = builtin_new
		return foo if length == 0
		n.times { foo << self }
		foo
	end

	def empty?
		length == 0
	end

	# do not use alias here.
	def size
		length
	end

	def reverse!; modify {
		return nil if length == 0
		j = length-1
		(length/2).times {|i|
			t = get(i)
			put(i,get(j))
			put(j,t)
			j-=1
		}
		self
	}end

# category: dup/replace wrappers for some methods

	def reverse; foo = dup; foo.reverse!; foo; end

# category: obscure things from Object class

	#!@#$ not used yet

	def fail_to_type(val,type)
		raise TypeError, "failed to convert %s into %s",
			case val
				when nil; "nil"
				when true,false; val.to_s
				else arg.val.to_s; end,
			type;
	end

	def convert_type(val,type,method)
		valtype = val.type
		return val if valtype == type
		begin
			val = val.send(method)
		rescue StandardError, NameError => e
			fail_to_type(val,type)
		end
		if val.type != type then
			raise TypeError, "%s#%s should return %s",
				valtype, method, type
		end
		val
	end
end

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