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

	MetaRuby
	file: Hollow String

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

=end

require "Contract"
require "Hollow/List"

module StringP; end
class String; include StringP; end

#--------------------------------------------------------------------------#
module SimpleStringP; module Contract
	include ::Contract

	# as far as I know, Strings work with bytes, not characters, I don't
	# know precisely. This may change in the future (?)
	# For most charsets it's not an issue anyway.
	# So let's say SimpleStringP works with bytes.
	# a byte here is unsigned and in the range 0...256

	# length() returns the number of characters (bytes??) in the string

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

	# like SimpleArrayP's, but the values are restricted to being bytes.

	def get(i)
		assert_index_excl(i)
		result = super
		assert_type("post: result",result,Fixnum)
		result
	end

	def put(i,v)
		assert_not_frozen
		assert_type("v",v,Fixnum)
		assert_index_excl(i)
		result = super
		assert_type("post: result",result,NilClass)
		nil
	end

	# like SimpleArrayP's, but with Strings instead of Arrays.
	def get_seq(i,n)
		assert_index_incl(i)
		assert_nonneg("n",n)
		assert_extent(i,n)
		result = super
		assert_type("post: result",result,String)
		result
	end

	def put_seq(i,n,v)
		assert_not_frozen
#		old_length = length
		assert_index_incl(i)
		assert_nonneg("n",n)
		assert_extent(i,n)
		assert_type("v",v,String)
		result = super
		assert_type("post: result",result,NilClass)
#		assert_eq("post: length",length,old_length-n+v.length)
		result
	end

	# note:
	# #dup() is no longer in StringP.
	# 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.
end end

#--------------------------------------------------------------------------#
# emulation of String using an implementation of StringP.

module HollowString
	include StringP
	include HollowList

# category: base

	def builtin_class;    String; end
	def builtin_new;      ""; end
	def to_builtin(v);    v.to_s; end
	def to_builtin2(v);   v.to_str; end
	def same_builtin?(v); StringP === v; end
	def get_default(i);	  nil; end # weird

	# private :builtin_class, :builtin_new, :to_builtin, :same_builtin?

	# unlike String's, has a default arg of ""
	def initialize(arg="")
		@frozen = false # not used yet
		replace arg
	end

# category: frozen

	# see also HollowList#modify
	def freeze; @frozen = true; end
	def frozen?; @frozen; end

# category: [] and []=

	def [](i,*args)
		case i
		when StringP
			foo = index(i); foo ? self[foo,i.length] : foo
		when Regexp
			r = i.match self
			nil==r ? nil : self[*(r.offset(0))]
		else
			super
		end
	end

	def length=(n)
		raise TypeError, "not defined for strings"
	end

	def []=(i,b,*args); modify {
		case i
		when StringP
			foo = index(i); foo ? self[foo,i.length]=b : foo
		when Regexp
			r = i.match self
			nil==r ? nil : self[*(r.offset(0))] = b
		when Integer
			if args.length==0 and (StringP===b) then
				super(i,1,b)
			else
				super
			end
		else
			super
		end
	}end

	def at_with_length(i,n)
		if i==length then nil else super end
	end

	def at_put(i,v)
		i += length if i < 0
		if not (0..length) === i then
			raise IndexError, "index %d out of string" % i
		end
		super
	end

	def at_with_length_put(i,n,v)
		if n < 0 then raise IndexError, "negative length" end
		i += length if i < 0
		if not (0..length) === i then
			raise IndexError, "index %d out of string" % i
		end
		super
	end

	# ruby 1.6
	alias slice []

	def slice!(i,*a); modify {
		# the test cases want this, despite "foobar"[6,1] returning ""
		if a.length==0 and Integer===i and not (-length...length)===i then
			raise IndexError, "index #{i} out of string"
		end
		super
	}end

# category: misc

	def ==(other)
		if $= then
			self.downcase.eq(other.downcase)
		else
			self.eq(other)
		end
	end

	def ===(other); self==other; end

	def %(arglist); to_str % arglist; end

	# from HollowArray
	def +(other)
		foo = dup
		foo.put_seq length,0,other.to_str
		foo
	end

	def replace(other); modify {
		put_seq 0,length,other.to_str
		self
	}end

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

	def center(n)
		n -= length
		if n > 0 then [' '*(n/2),' '*(n-n/2)].join(self) else self end
	end

	def ljust(n)
		n -= length
		if n > 0 then self + ' '*n else self end
	end

	def rjust(n)
		n -= length
		if n > 0 then ' '*n + self else self end
	end

	def crypt(salt)
		to_str.crypt salt
	end

	#!@#$ fix me
	def dump
#		gsub(/[\000-\037\177-\377\"\\]/) {|x|
#			if    x[0]==10 then "\\n"
#			elsif x[0]==92 then "\\\\"
#			elsif x[0]==34 then "\\\""
#			else
#				format("\\x%02x",x[0])
#			end                                   
#		}

		to_str.dump
	end

	def hash
		h = 0
		each_byte {|v|
			h = (h<<4) + v
			h &= 2**32-1
			g = h & 0xf0000000
			if g > 0 then
				h ^= g >> 24
			end
			h &= ~g
		}
		h
	end

	def intern; to_str.intern; end

	def succ!; modify { replace(to_str.succ) }end

	alias next! succ!

	def unpack(*args)
		self[0...length].unpack(*args)
	end

	def inspect
#		sprintf "S(%s)", to_str.inspect
		to_str.inspect
#		"\"" + dump() + "\""
	end

	def reverse!; modify { super } end

# category: iterators

	def each(other=$/,&proc)
		## don't laugh, it works
		#other = if other == '' then "\n+" else Regexp.quote(other.reverse) end
		#foo = reverse.split(/(?=#{other})/).reverse.map{|x|x.reverse}
		#STDERR.puts ""
		#STDERR.puts foo.inspect
		#foo.each(&proc)

		to_str.each(other,&proc)	
	end

	# copied from HollowArray#each
	def each_byte(&proc)
		length.times {|i|
			proc.call get(i)
		}
		self
	end

	alias each_line each

	def upto(to,&proc)
		to_str.upto(to,&proc)
	end

# category: character-related

	def capitalize!; modify {
		foo = downcase
		foo[0..0] = foo[0..0].upcase
		self == foo ? nil : replace(foo)
	}end

	def downcase!; modify { tr!    'A-Z','a-z'    }end
	def upcase!;   modify { tr!    'a-z','A-Z'    }end
	def swapcase!; modify { tr! 'a-zA-Z','A-Za-z' }end

# category: number-related

	def hex;  to_str.hex;  end
	def oct;  to_str.oct;  end
	def to_f; to_str.to_f; end
	def to_i; to_str.to_i; end

	# copies of HollowArray#to_a,to_ary
	# can't say just "self"
	def to_s  ; get_seq 0,length; end
	def to_str; get_seq 0,length; end

	#!@#$ huh???
	# this has no testcase?
	def sum(bits=16)
#		h = 0; each_byte {|b| h += b }; h
		to_str.sum(bits=16)
	end

# category: regexps	

	def =~(other)
		case other
		when Regexp; other =~ self.to_str
		when StringP; Regexp.new(other) =~ self.to_str
		else; other.=~(self)
		end
	end

	# it's your own fault for using ~
	def ~
		# setting up quantum superposition
		r,w = IO.pipe
		if not fork then
			set_trace_func(proc{|*a|
				w.puts(eval("$_", a[4]).inspect)
				exit!
			})
		else
			Process.wait
			eval(r.readline) =~ self.to_str 
		end
	end

	def chomp!(other=$/); modify {
		if rindex(other) == length - other.length then
			self[length - other.length] = ""
			self
		else
			nil
		end
	}end

	def include?(other)
		case other
		when StringP; Integer === index(other)
		when Integer; Integer === index(other.chr)
		when Regexp;  !!( other =~ self )
		end
	end

	def index (other,offset=0)
		other = other.to_str if HollowString===other
		to_str.index(other,offset); end

	def rindex(other,offset=-1)
		other = other.to_str if HollowString===other
		to_str.rindex(other,offset); end

	def count(arg1,*args); to_str.count(arg1,*args); end
	def scan(pattern,&proc);	to_str.scan(pattern,&proc); end

	def split(*args)
		if args.length == 0 then
			args[0] = $; || ' '
		else
			args[0] = args[0].to_str if HollowString===args[0]
		end
		to_str.split(*args)
	end

	def squeeze!(*args); modify {
		foo = length
		replace(to_str.squeeze(*args))
		if self.length == foo then nil else self end
	}end

	def delete!(arg1,*args); modify {
		foo = length
		replace(to_str.delete(arg1,*args))
		if self.length == foo then nil else self end
	}end

	#!@#$ something wrong with $1,$2,...
	def gsub!(pattern,*arg,&proc); modify {
		pattern=pattern.to_str if HollowString===pattern
		arg[0]=arg[0].to_str if arg.length>0 and HollowString===arg[0]
		r = to_str.gsub!(pattern,*arg,&proc)
		return replace r if r
		nil
	}end

	#!@#$ something wrong with $1,$2,...
	def sub! (pattern,*arg,&proc); modify {
		pattern=pattern.to_str if HollowString===pattern
		arg[0]=arg[0].to_str if arg.length>0 and HollowString===arg[0]
		r = to_str.sub!(pattern,*arg,&proc)
		return replace r if r
		nil
	}end

	def strip!; modify {
#		self.sub!(/^\s+/,'')
#		self.sub!(/\s+$/,'')
		foo = to_str.strip
		self == foo ? nil : replace(foo)
	}end

# category: tr

	def tr!(from,to); modify {
		foo = to_str.tr(from,to)
		self.eq(foo) ? nil : replace(foo)
	}end

	def tr_s!(from,to); modify {
		foo = to_str.tr_s(from,to)
		self.eq(foo) ? nil : replace(foo)
	}end

# category: stack/queue

	# almost like HollowArray#pop
	def chop!; modify {
		return nil if length == 0
		if length > 1 && get_seq(length-2,2) == "\r\n" then
			put_seq length-2,2,builtin_new
		else
			put_seq length-1,1,builtin_new
		end
		self
	}end

	def <<(v); modify {
		put_seq length,0, Fixnum===v ? v.chr : v.to_str
		self
	}end

	alias concat <<

# category: dup/replace wrappers for some methods

	def chop; foo = dup; foo.chop!; foo; end
	def chomp(other=$/); foo = dup; foo.chomp!(other); foo; end
	def strip; foo = dup; foo.strip!; foo; end

	def capitalize; foo = dup; foo.capitalize!; foo; end
	def downcase;   foo = dup; foo.downcase!;   foo; end
	def swapcase;   foo = dup; foo.swapcase!;   foo; end
	def upcase;     foo = dup; foo.upcase!;     foo; end

	def squeeze(*args);    foo = dup; foo.squeeze!(*args);    foo; end
	def delete(arg,*args); foo = dup; foo.delete!(arg,*args); foo; end
	def gsub(pattern,*args,&proc); foo = dup; foo.gsub!(pattern,*args,&proc); foo; end
	def  sub(pattern,*args,&proc); foo = dup; foo .sub!(pattern,*args,&proc); foo; end

	def succ; foo = dup; foo.succ!; foo; end
	alias next succ

	def tr  (from,to); foo = dup;   foo.tr!(from,to); foo; end
	def tr_s(from,to); foo = dup; foo.tr_s!(from,to); foo; end
end

#--------------------------------------------------------------------------#
# not sure whether I really should do such things,
# but I think String should cooperate.

class String
	unless instance_methods.include?(:orig_equality)
		alias orig_equality ==
		def ==(other)
			if HollowString===other then
				other==self
			else
				orig_equality(other)
			end
		end
	end
end

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

class AutoString

	def initialize(arg="")
		@inner = ""
		super
	end

	module Implementation; include SimpleStringP
		def length; @inner.length; end
		def get(i); @inner[i]; end
		def put(i,v); @inner[i]=v; nil; end
		def get_seq(i,n); @inner[i,n]; end
		def put_seq(i,n,v); @inner[i,n]=v; nil; end
	end

	include HollowString
	include Implementation
	include SimpleStringP::Contract
	
end	

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

#$c = AutoString
#$d = $c.new("Fum$")
#$_ = $c.new("FeeFieFoo-Fum")
