############################################################################
#
#	File:     bitstr.icn
#
#	Subject:  Procedures for bits in Icon strings
#
#	Author:   Robert J. Alexander
#
#	Date:     August 14, 1996
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  Procedures for working with strings made up of numeric values
#  represented by strings of an arbitrary number of bits, stored without
#  regard to character boundaries.
#
#  In conjunction with the "large integers" feature of Icon, this
#  facility can deal with bitstring segments of arbitrary size.  If
#  "large integers" are not supported, bitstring segments (i.e.  the
#  nbits parameter of BitStringGet and BitStringPut) wider that the
#  integer size of the platform are likely to produce incorrect results.
#
############################################################################
#
#  Usage of BitStringPut, by example:
#
#       record bit_value(value, nbits)
#       ...
#       bitString := BitString("")
#       while value := get_new_value() do       # loop to append to string
#               BitStringPut(bitString, value.nbits, value.value)
#       resultString := BitStringPut(bitString) # output any buffered bits
#
#  Note the interesting effect that BitStringPut(bitString), as well as
#  producing the complete string, pads the buffered string to an even
#  character boundary.  This can be dune during construction of a bit
#  string if the effect is desired.
#
#  The "value" argument defaults to zero.
#
############################################################################
#
#  Usage of BitStringGet, by example:
#
#       record bit_value(value, nbits)
#       ...
#       bitString := BitString(string_of_bits)
#       while value := BitStringGet(bitString, nbits) do
#               # do something with value
#
#  BitStringGet fails when too few bits remain to satisfy a request.
#  However, if bits remain in the string, subsequent calls with fewer
#  bits requested may succeed.  A negative "nbits" value gets the value
#  of the entire remainder of the string, to the byte boundary at its
#  end.
#
############################################################################
#
#  See also: bitstrm.icn
#
############################################################################

record BitString(s, buffer, bufferBits)

procedure BitStringPut(bitString, nbits, value)
    local outvalue
    #
    #  Initialize.
    #
    /bitString.buffer := bitString.bufferBits := 0
    #
    #  If this is "close" call ("nbits" is null), flush buffer,
    #  reinitialize, and return the bit string with the final character
    #  value zero padded on the right.
    #
    if /nbits then {
	 if bitString.bufferBits > 0 then
		  bitString.s ||:=
			   char(ishift(bitString.buffer, 8 - bitString.bufferBits))
	 bitString.buffer := bitString.bufferBits := 0
	 return bitString.s
	    }
    #
    #  Merge new value into buffer.
    #
    /value := 0
    bitString.buffer := ior(ishift(bitString.buffer, nbits), value)
    bitString.bufferBits +:= nbits
    #
    #  Output bits.
    #
    while bitString.bufferBits >= 8 do {
	 bitString.s ||:= char(outvalue :=
	       ishift(bitString.buffer, 8 - bitString.bufferBits))
	 bitString.buffer :=
		  ixor(bitString.buffer, ishift(outvalue, bitString.bufferBits - 8))
	 bitString.bufferBits -:= 8
	    }
    return
end


procedure BitStringGet(bitString, nbits)
    local value, save, i
    #
    #  Initialize.
    #
    /bitString.buffer := bitString.bufferBits := 0
    #
    #  Get more data if necessary.
    #
    save := copy(bitString)
    while nbits < 0 | bitString.bufferBits < nbits do {
	 (bitString.buffer :=
		  ior(ishift(bitString.buffer, 8), ord(bitString.s[1]))) | {
	     #
	     #  There aren't enough bits left in the file.  Restore the
	     #  BitString to its state before the call (in case he wants to
	     #  try again), and fail.
	     #
	     if nbits >= 0 then {
		  every i := 1 to *bitString do
			   bitString[i] := save[i]
		  fail
		     }
	     else {
		  bitString.s := ""
		  bitString.bufferBits := value := 0
		  value :=: bitString.buffer
		  return value
		     }
	        }
	 bitString.s[1] := ""
	 bitString.bufferBits +:= 8
	    }
    #
    #  Extract value from buffer and return.
    #
    value := ishift(bitString.buffer, nbits - bitString.bufferBits)
    bitString.buffer :=
	     ixor(bitString.buffer, ishift(value, bitString.bufferBits - nbits))
    bitString.bufferBits -:= nbits
    return value
end
