// NpgsqlTypes\ArrayHandling.cs
//
// Author:
// Jon Hanna. (jon@hackcraft.net)
//
// Copyright (C) 2008 The Npgsql Development Team
// npgsql-general@gborg.postgresql.org
// http://gborg.postgresql.org/project/npgsql/projdisplay.php
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace NpgsqlTypes
{
///
/// Implements a bit string; a collection of zero or more bits which can each be 1 or 0.
/// BitString's behave as a list of bools, though like most strings and unlike most collections the position
/// tends to be of as much significance as the value.
/// BitStrings are often used as masks, and are commonly cast to and from other values.
///
public struct BitString : IList, IEquatable, IComparable, IComparable, IFormattable, IConvertible
{
///
/// Represents the empty string.
///
public static readonly BitString Empty = new BitString(new List(0), 0);
private readonly List _chunks;
private readonly int _lastChunkLen;
///
/// Create a BitString from an enumeration of boolean values. The BitString will contain
/// those booleans in the order they came in.
///
/// The boolean values.
public BitString(IEnumerable bits)
{
_chunks = new List();
int curChunkLen = 0;
uint curChunk = 0;
foreach(bool bit in bits)
{
curChunk = (curChunk << 1) | (bit ? 1u : 0u);
if(++curChunkLen == 32)
{
_chunks.Add(curChunk);
curChunk = 0;
curChunkLen = 0;
}
}
if(curChunkLen != 0)
_chunks.Add(curChunk << -curChunkLen);
_lastChunkLen = curChunkLen;
}
//Used for optimised internal creation. The last chunk must be zero'd at bits less significant than lastChunkLen or comparisons will fail.
private BitString(List chunks, int lastChunkLen)
{
_chunks = chunks;
_lastChunkLen = lastChunkLen;
}
///
/// Creates a BitString filled with a given number of true or false values.
///
/// The value to fill the string with.
/// The number of bits to fill.
public BitString(bool value, int count)
{
if(value)
{
_chunks = new List((count + 31) / 32);
for(int i = 0; i < count / 32; ++i)
_chunks.Add(0xFFFFFFFFu);
_chunks.Add(0xFFFFFFFFu << - count);
}
else
_chunks = new List(new uint[(count + 31) / 32]);
_lastChunkLen = count % 32;
}
///
/// Creats a bitstring from a string.
/// The string to copy from.
///
///
public BitString(string str)
{
BitString fromParse = Parse(str);
_chunks = fromParse._chunks;
_lastChunkLen = fromParse._lastChunkLen;
}
///
/// Creates a single-bit element from a boolean value.
///
/// The bool value which determines whether
/// the bit is 1 or 0.
public BitString(bool boolean)
:this(boolean, 1){}
///
/// Creates a bitstring from an unsigned integer value. The string will be the shortest required to
/// contain the integer (e.g. 1 bit for 0 or 1, 2 for 2 or 3, 3 for 4-7, and so on).
///
/// The integer.
/// This method is not CLS Compliant, and may not be available to some languages.
[CLSCompliant(false)]
public BitString(uint integer)
{
int bitCount = 32;
while(bitCount >= 1 && (integer & 0x80000000u) == 0)
{
integer <<= 1;
--bitCount;
}
_chunks = new List(1);
_chunks.Add(integer);
_lastChunkLen = bitCount;
}
///
/// Creates a bitstring from an integer value. The string will be the shortest required to
/// contain the integer (e.g. 1 bit for 0 or 1, 2 for 2 or 3, 3 for 4-7, and so on).
///
/// The integer.
public BitString(int integer)
:this((uint)integer){}
private IEnumerable AllChunksButLast
{
get
{
if(_chunks.Count > 1)
return _chunks.GetRange(1, _chunks.Count - 1);
else
return new uint[]{};
}
}
private IEnumerable EnumChunks(bool includeLast)
{
return includeLast ? _chunks : AllChunksButLast;
}
///
/// The length of the string.
///
public int Length
{
get
{
return (_chunks.Count - (_lastChunkLen == 0 ? 0 : 1)) * 32 + _lastChunkLen;
}
}
///
/// Retrieves the value of the bit at the given index.
///
public bool this[int index]
{
get
{
if(index < 0 || index >= Length)
throw new ArgumentOutOfRangeException();
return (_chunks[index / 32] & (1 << (31 - index % 32))) != 0;
}
}
bool IList.this[int idx]
{
get
{
return this[idx];
}
set
{
throw new NotSupportedException();
}
}
int ICollection.Count
{
get
{
return Length;
}
}
bool ICollection.IsReadOnly
{
get
{
return true;
}
}
///
/// Finds the first instance of a given value
///
/// The value - whether true or false - to search for.
/// The index of the value found, or -1 if none are present.
public int IndexOf(bool item)
{
if(item)
{
for(int chunkCount = 0; chunkCount != _chunks.Count; ++chunkCount)
{
if(_chunks[chunkCount] != 0)
{
uint chunk = _chunks[chunkCount];
for(int i = 0; i != 32; ++i)
{
if((chunkCount & (0x80000000u >> i)) != 0)
{
return chunkCount * 32 + i;
}
}
}
}
}
else
{
for(int chunkCount = 0; chunkCount != _chunks.Count; ++chunkCount)
{
if(_chunks[chunkCount] != 0xFFFFFFFFu)
{
uint chunk = _chunks[chunkCount];
for(int i = 0; i != 32; ++i)
{
if((~chunkCount & (0x80000000u >> i)) != 0)
{
int ret = chunkCount * 32 + i;
return ret < Length ? ret : -1;
}
}
}
}
}
return -1;
}
void IList.Insert(int index, bool item)
{
throw new NotSupportedException();
}
void IList.RemoveAt(int index)
{
throw new NotSupportedException();
}
void ICollection.Add(bool item)
{
throw new NotSupportedException();
}
void ICollection.Clear()
{
throw new NotSupportedException();
}
///
/// True if there is at least one bit with the value looked for.
///
/// The value - true or false - to detect.
/// True if at least one bit was the same as item, false otherwise.
public bool Contains(bool item)
{
foreach(uint chunk in EnumChunks(item))//because last chunk is zero-filled in unused portion, it is safe to check it if searching for true
if(item && (chunk != 0) || !item && chunk != 0xFFFFFFFFu)
return true;
return !item && (_chunks[_chunks.Count - 1] & (0xFFFFFFFFu >> _lastChunkLen)) != 0;
}
///
/// Copies the bitstring to an array of bools.
///
/// The boolean array to copy to.
/// The index in the array to start copying from.
public void CopyTo(bool[] array, int arrayIndex)
{
if(array == null)
throw new ArgumentNullException();
if(arrayIndex < 0)
throw new ArgumentOutOfRangeException();
if(array.Rank != 1
|| arrayIndex >= array.Length
|| arrayIndex + Length > array.Length)
throw new ArgumentException();
foreach(bool bit in this)
array[arrayIndex++] = bit;
}
bool ICollection.Remove(bool item)
{
throw new NotSupportedException();
}
///
/// Returns an enumerator that enumerates through the string.
///
/// The enumerator.
public IEnumerator GetEnumerator()
{
if(_chunks.Count != 0)
{
foreach(uint chunk in EnumChunks(_lastChunkLen == 0))
for(int i = 31; i != -1; --i)
yield return (chunk & (1u << i)) != 0;
uint lastChunk = _chunks[_chunks.Count - 1];
for(int i = 31; i != 31 - _lastChunkLen; --i)
yield return (lastChunk & (1u << i)) != 0;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
///
/// Creats a bitstring by concatenating another onto this one.
///
/// The string to append to this one.
/// The combined strings.
public BitString Concat(BitString append)
{
if(Length == 0)
return append;
else if(append.Length == 0)
return this;
else if(_lastChunkLen == 0)//Not only more efficient case with special handling, but also reasonably likely to appear in real use quite often.
{
List chunks = new List(_chunks);//Note that this copies, doesn't share.
chunks.AddRange(append._chunks);
return new BitString(chunks, append._lastChunkLen);
}
else
{
List chunks = new List(AllChunksButLast);
chunks.Add(_chunks[_chunks.Count - 1] | (append._chunks[0] >> _lastChunkLen));
for(int idx = 1; idx != append._chunks.Count; ++idx)
{
chunks.Add((append._chunks[idx - 1] << -_lastChunkLen) | (append._chunks[idx] >> _lastChunkLen));
}
chunks.Add(append._chunks[append._chunks.Count - 1] << -_lastChunkLen);
while(chunks.Count > (Length + append.Length + 31) / 32)
chunks.RemoveAt(chunks.Count - 1);
return new BitString(chunks, (_lastChunkLen + append._lastChunkLen) % 32);
}
}
///
/// Returns a substring of this string.
///
/// The position to start from, must be between 0 and the length of the string.
/// The length of the string to return, must be greater than zero, and may not be
/// so large that the start + length exceeds the bounds of this instance.
/// The Bitstring identified
public BitString Substring(int start, int length)
{
if(start < 0 || length < 0 || start + length > Length)
throw new ArgumentOutOfRangeException();
else if(length == 0)
return Empty;
else if(start == 0 && length == Length)
return this;
else if(start % 32 == 0)
{
List chunks = _chunks.GetRange(start / 32, (length + 31) / 32);
if(length % 32 != 0)
chunks[chunks.Count - 1] = chunks[chunks.Count -1] & (0xFFFFFFFFu << -length);
return new BitString(chunks, length % 32);
}
else
{
List chunks = new List();
for(int i = start / 32 + 1; i < (start + length) / 32 + 1; ++i)
chunks.Add((_chunks[i - 1] << start) | (_chunks[i] >> -start));
if(length % 32 != 0 && chunks.Count < length / 32 + 1)
chunks.Add((_chunks[(start + length - 1) / 32] << start) & (0xFFFFFFFFu << -length));
return new BitString(chunks, length % 32);
}
}
///
/// Returns a substring of this string.
///
/// The position to start from, must be between 0 and the length of the string,
/// the rest of the string is returned.
/// The Bitstring identified
public BitString Substring(int start)
{
return Substring(start, Length - start);
}
///
/// A logical and between this string and another. The two strings must be the same length.
///
/// Another BitString to AND with this one.
/// A bitstring with 1 where both BitStrings had 1 and 0 otherwise.
public BitString And(BitString operand)
{
if(_lastChunkLen != operand._lastChunkLen || _chunks.Count != operand._chunks.Count)
throw new ArgumentException("Cannot AND bitstrings of different sizes");
List chunks = new List(_chunks.Count);
for(int i = 0; i != _chunks.Count; ++i)
chunks.Add(_chunks[i] & operand._chunks[i]);
return new BitString(chunks, _lastChunkLen);
}
///
/// A logical or between this string and another. The two strings must be the same length.
///
/// Another BitString to OR with this one.
/// A bitstring with 1 where either BitString had 1 and 0 otherwise.
public BitString Or(BitString operand)
{
if(_lastChunkLen != operand._lastChunkLen || _chunks.Count != operand._chunks.Count)
throw new ArgumentException("Cannot OR bitstrings of different sizes");
List chunks = new List(_chunks.Count);
for(int i = 0; i != _chunks.Count; ++i)
chunks.Add(_chunks[i] | operand._chunks[i]);
return new BitString(chunks, _lastChunkLen);
}
///
/// A logical xor between this string and another. The two strings must be the same length.
///
/// Another BitString to XOR with this one.
/// A bitstring with 1 where one BitStrings and the other had 0,
/// and 0 where they both had 1 or both had 0.
public BitString Xor(BitString operand)
{
if(_lastChunkLen != operand._lastChunkLen || _chunks.Count != operand._chunks.Count)
throw new ArgumentException("Cannot XOR bitstrings of different sizes");
List chunks = new List(_chunks.Count);
for(int i = 0; i != _chunks.Count; ++i)
chunks.Add(_chunks[i] ^ operand._chunks[i]);
return new BitString(chunks, _lastChunkLen);
}
///
/// A bitstring that is the logical inverse of this one.
///
/// A bitstring of the same length as this with 1 where this has 0 and vice-versa.
public BitString Not()
{
List chunks = new List(_chunks.Count);
foreach(uint chunk in _chunks)
chunks.Add(~chunk);
chunks[chunks.Count - 1] = chunks[chunks.Count - 1] & (0xFFFFFFFFu << -_lastChunkLen);
return new BitString(chunks, _lastChunkLen);
}
///
/// Shifts the string operand bits to the left, filling with zeros to produce a
/// string of the same length.
///
/// The number of bits to shift to the left.
/// A left-shifted bitstring.
/// The behaviour of LShift is closer to what one would expect from dealing
/// with PostgreSQL bit-strings than in using the same operations on integers in .NET
/// In particular, negative operands result in a right-shift, and operands greater than
/// the length of the string will shift it entirely, resulting in a zero-filled string.
///
public BitString LShift(int operand)
{
if(_chunks.Count == 0)
return Empty;
else if(operand < 0)
return RShift(-operand);
else if(operand == 0)
return this;
else if(operand >= Length)
return new BitString(false, Length);
else if(operand % 32 == 0)
{
List chunks = _chunks.GetRange(operand / 32, (Length - operand) / 32);
chunks.AddRange(new uint[_chunks.Count - chunks.Count]);
return new BitString(chunks, _lastChunkLen);
}
else
{
List chunks = new List();
for(int idx = (operand + 31) / 32; idx != _chunks.Count; ++idx)
chunks.Add((_chunks[idx - 1] << operand) | (_chunks[idx] >> -operand));
chunks.Add(_chunks[_chunks.Count - 1] << operand);
chunks.AddRange(new uint[_chunks.Count - chunks.Count]);
return new BitString(chunks, _lastChunkLen);
}
}
///
/// Shifts the string operand bits to the right, filling with zeros to produce a
/// string of the same length.
///
/// The number of bits to shift to the right.
/// A right-shifted bitstring.
/// The behaviour of RShift is closer to what one would expect from dealing
/// with PostgreSQL bit-strings than in using the same operations on integers in .NET
/// In particular, negative operands result in a left-shift, and operands greater than
/// the length of the string will shift it entirely, resulting in a zero-filled string. It also performs
/// a logical shift, rather than an arithmetic shift, so it always sets the vacated bit positions to zero
/// (like PostgreSQL and like .NET for unsigned integers but not for signed integers).
///
public BitString RShift(int operand)
{
if(_chunks.Count == 0)
return Empty;
else if(operand < 0)
return LShift(-operand);
else if(operand == 0)
return this;
else if(operand >= Length)
return new BitString(false, Length);
else if(operand % 32 == 0)
{
List chunks = _chunks.GetRange(0, _chunks.Count - operand / 32);
chunks.InsertRange(0, new uint[_chunks.Count - chunks.Count]);
return new BitString(chunks, _lastChunkLen);
}
else
{
List chunks = new List();
chunks.Add(_chunks[0] >> operand);
for(int idx = 0; idx != (_chunks.Count - operand /32) - 1; ++idx)
chunks.Add((_chunks[idx] << -operand) | (_chunks[idx + 1] >> operand));
chunks.InsertRange(0, new uint[_chunks.Count - chunks.Count]);
return new BitString(chunks, _lastChunkLen);
}
}
///
/// Returns true if the this string is identical to the argument passed.
///
public bool Equals(BitString other)
{
if(null == (object)other)
return false;
if(ReferenceEquals(_chunks, other._chunks))//short cut on shallow copies
return true;
if(_lastChunkLen != other._lastChunkLen || _chunks.Count != other._chunks.Count)
return false;
for(int i = 0; i != _chunks.Count; ++i)
if(_chunks[i] != other._chunks[i])
return false;
return true;
}
///
/// Compares two strings. Strings are compared as strings, so while 0 being less than 1 will
/// mean a comparison between two strings of the same size is the same as treating them as numbers,
/// in the case of two strings of differing lengths the comparison starts at the right-most (most significant)
/// bit, and if all bits of the shorter string are exhausted without finding a comparison, then the larger
/// string is deemed to be greater than the shorter (0010 is greater than 0001 but less than 00100).
///
/// Another string to compare with this one.
/// A value if the two strings are identical, an integer less
/// than zero if this is less than the argument, and an integer greater
/// than zero otherwise.
public int CompareTo(BitString other)
{
if(null == (object)other)
return 1;
int endAt = Math.Min(_chunks.Count, other._chunks.Count);
int cmp = 0;
for(int i = 0; i != endAt; ++i)
if((cmp = _chunks[i].CompareTo(other._chunks[i])) != 0)
return cmp;
return _lastChunkLen.CompareTo(other._lastChunkLen);
}
///
/// Compares the string with another object.
///
/// The object to compare with.
/// If the object is null then this string is considered greater. If the object is another BitString
/// then they are compared as in the explicit comparison for BitStrings
/// in any other case a is thrown.
public int CompareTo(object obj)
{
if(null == obj)
return 1;
if(!(obj is BitString))
throw new ArgumentException();
return CompareTo((BitString)obj);
}
///
/// Compares this BitString with an object for equality.
///
public override bool Equals(object obj)
{
return obj is BitString && Equals((BitString)obj);
}
///
/// Returns a code for use in hashing operations.
///
public override int GetHashCode()
{
int ret = _lastChunkLen;
//The ideal amount to shift each value is one that would evenly spread it throughout
//the resultant bytes. Using the current result % 32 is essentially using a random value
//but one that will be the same on subsequent calls.
foreach(uint chunk in _chunks)
ret ^= Npgsql.PGUtil.RotateShift((int)chunk, ret % 32);
return ret;
}
private StringBuilder BFormatString()
{
StringBuilder sb = new StringBuilder();
foreach(bool bit in this)
sb.Append(bit ? '1' : '0');
return sb;
}
private StringBuilder XFormatString(bool upperCase, bool ignoreTrailingBits)
{
if(!ignoreTrailingBits && _lastChunkLen % 4 != 0)
throw new FormatException();
StringBuilder sb = new StringBuilder();
foreach(int chunk in _chunks)
sb.Append(chunk.ToString(upperCase ? "X8" : "x8"));
if(_lastChunkLen != 0)
sb.Length -= (32 - _lastChunkLen + 3) / 4;
return sb;
}
private static StringBuilder ZeroPad(StringBuilder str, int padTo)
{
int padBy = padTo - str.Length;
while(padBy-- > 0)
str.Insert(0, '0');
return str;
}
///
/// Returns a string representation of the BitString.
///
///
/// A string which can contain a letter and optionally a number which sets a minimum size for the string
/// returned. In each case using the lower-case form of the letter will result in a lower-case string
/// being returned.
///
/// -
/// B
/// A string of 1s and 0s.
///
/// -
/// X
/// An hexadecimal string (will result in an error unless the string's length is divisible by 4).
///
/// -
/// G
/// A string of 1s and 0s in single-quotes preceded by 'B' (Postgres bit string literal syntax).
///
/// Y
/// An hexadecimal string in single-quotes preceded by 'X' (Postgres bit literal syntax, will result in an error unless the string's length is divisible by 4.
///
/// C
/// The format produced by format-string "Y" if legal, otherwise that produced by format-string "G".
/// E
/// The most compact safe representation for Postgres. If single bit will be either a 0 or a 1. Otherwise if it
/// can be that produce by format string "Y" it will, otherwise if there are less than 9bits in length it will be that
/// produced by format-string "G". For longer strings that cannot be represented in hexadecimal it will be a string
/// representing the first part of the string in format "Y" followed by the PostgreSQL concatenation operator, followed
/// by the final bits in the format "G". E.g. "X'13DCE'||B'110'"
/// If format is empty or null, it is treated as if "B" had been passed (the default repreesentation, and that
/// generally used by PostgreSQL for display).
///
/// The formatted string.
public string ToString(string format)
{
format = string.IsNullOrEmpty(format) ? "B" : format.Trim();
int padTo = int.Parse("0" + format.Substring(1));
switch(format[0])
{
case 'b': case 'B':
return ZeroPad(BFormatString(), padTo).ToString();
case 'x':
return ZeroPad(XFormatString(false, false), padTo).ToString();
case 'X':
return ZeroPad(XFormatString(true, false), padTo).ToString();
case 'g':
return ZeroPad(BFormatString(), padTo).Insert(0, "b'").Append('\'').ToString();
case 'G':
return ZeroPad(BFormatString(), padTo).Insert(0, "B'").Append('\'').ToString();
case 'y':
return ZeroPad(XFormatString(false, false), padTo).Insert(0, "x'").Append('\'').ToString();
case 'Y':
return ZeroPad(XFormatString(true, false), padTo).Insert(0, "X'").Append('\'').ToString();
case 'c':
return ToString((_lastChunkLen %4 == 0 ? "y" : "g") + padTo.ToString());
case 'C':
return ToString((_lastChunkLen %4 == 0 ? "Y" : "G") + padTo.ToString());
case 'e':
return ToString("E" + padTo.ToString()).ToLowerInvariant();
case 'E':
if(_lastChunkLen == 1 && _chunks.Count == 1)
return (_chunks[0] & 0x80000000u) == 0 ? "0" : "1";//both safe in this case for all lengths, and allows for some backwards compatibility from threating bit(1) as if it were boolean.
else if(_lastChunkLen % 4 == 0)
return ToString("Y" + padTo.ToString());
else if(Length < 9)
return ToString("G" + padTo.ToString());
else
{
StringBuilder sb = XFormatString(true, true).Insert(0, "X'");
sb.Append("\'||B\'");
uint lastNibble = _chunks[_chunks.Count - 1] << (_lastChunkLen / 4 * 4);
for(int i = 0; i < _lastChunkLen % 4; ++i)
{
uint mask = 0x80000000u >> i;
sb.Append((lastNibble & mask) != 0 ? '1' : '0');
}
return sb.Append('\'').ToString();
}
default:
throw new FormatException();
}
}
///
/// Returns a string representation for the Bitstring
///
/// A string containing '0' and '1' characters.
public override string ToString()
{
return ToString("B");
}
///
/// Returns the same string as . formatProvider is ignored.
///
public string ToString(string format, IFormatProvider formatProvider)
{
return ToString(format);
}
private static IEnumerable ReadBinary(TextReader tr)
{
for(;;)
switch(tr.Peek())
{
case -1: case (int)'\'':
yield break;
case (int)'0':
tr.Read();
yield return false;
break;
case (int)'1':
tr.Read();
yield return true;
break;
default:
throw new FormatException();
}
}
private static IEnumerable ReadHexNibbles(TextReader tr)
{
for(;;)
switch(tr.Peek())
{
case -1: case (int)'\'':
yield break;
case (int)'0': case (int)'1': case (int)'2': case (int)'3':
case (int)'4': case (int)'5': case (int)'6': case (int)'7':
case (int)'8': case (int)'9':
yield return (uint)tr.Read() - '0';
break;
case (int)'a': case (int)'b': case (int)'c': case (int)'d':
case (int)'e': case (int)'f':
yield return (uint)tr.Read() - 'a' + 10;
break;
case (int)'A': case (int)'B': case (int)'C': case (int)'D':
case (int)'E': case (int)'F':
yield return (uint)tr.Read() - 'A' + 10;
break;
default:
throw new FormatException();
}
}
private static BitString Parse(TextReader tr)
{
List parts = new List();
for(;;)
switch(tr.Peek())
{
case (int)'0': case (int)'1':
parts.Add(new BitString(ReadBinary(tr)));
break;
case (int)'x': case (int)'X':
tr.Read();
if(tr.Read() != (int)'\'')
throw new FormatException();
int nibbleCount = 0;
uint currentChunk = 0;
List chunks = new List();
foreach(uint nibble in ReadHexNibbles(tr))
{
currentChunk = currentChunk << 4 | nibble;
if(++nibbleCount == 8)
{
chunks.Add(currentChunk);
nibbleCount = 0;
}
}
if(nibbleCount != 0)
chunks.Add(currentChunk << 32 - nibbleCount * 4);
parts.Add(new BitString(chunks, nibbleCount * 4));
break;
case (int)'\'': case (int)'b': case (int)'B': case (int)'|':
case (int)' ': case (int)'\n': case (int)'\r': case '\t':
tr.Read();
break;
case -1:
switch(parts.Count)
{
case 0:
return Empty;
case 1:
return parts[0];
default:
BitString accum = parts[0];
for(int i = 1; i != parts.Count; ++i)
accum = accum.Concat(parts[i]);
return accum;
}
default:
throw new FormatException();
}
}
///
/// Parses a string to produce a BitString. Most formats that can be produced by
/// can be accepted, but hexadecimal
/// can be interpreted with the preceding X' to mark the following characters as
/// being hexadecimal rather than binary.
///
public static BitString Parse(string text)
{
using(StringReader sr = new StringReader(text))
return Parse(sr);
}
///
/// Performs a logical AND on the two operands.
///
public static BitString operator&(BitString x, BitString y)
{
return x.And(y);
}
///
/// Performs a logcial OR on the two operands.
///
public static BitString operator|(BitString x, BitString y)
{
return x.Or(y);
}
///
/// Perofrms a logical EXCLUSIVE-OR on the two operands
///
public static BitString operator^(BitString x, BitString y)
{
return x.Xor(y);
}
///
/// Performs a logical NOT on the operand.
///
public static BitString operator~(BitString x)
{
return x.Not();
}
///
/// Concatenates the operands.
///
public static BitString operator+(BitString x, BitString y)
{
return x.Concat(y);
}
///
/// Left-shifts the string BitString.
///
public static BitString operator<<(BitString bs, int shift)
{
return bs.LShift(shift);
}
///
/// Right-shifts the string BitString.
///
public static BitString operator>>(BitString bs, int shift)
{
return bs.RShift(shift);
}
///
/// Compares the two operands.
///
public static bool operator==(BitString x, BitString y)
{
return x.Equals(y);
}
///
/// Compares the two operands.
///
public static bool operator!=(BitString x, BitString y)
{
return !x.Equals(y);
}
///
/// Compares the two operands.
///
public static bool operator<(BitString x, BitString y)
{
return x.CompareTo(y) < 0;
}
///
/// Compares the two operands.
///
public static bool operator>(BitString x, BitString y)
{
return x.CompareTo(y) > 0;
}
///
/// Compares the two operands.
///
public static bool operator<=(BitString x, BitString y)
{
return x.CompareTo(y) <= 0;
}
///
/// Compares the two operands.
///
public static bool operator>=(BitString x, BitString y)
{
return x.CompareTo(y) >= 0;
}
public TypeCode GetTypeCode()
{
return TypeCode.Object;
}
public bool ToBoolean()
{
if(Length != 1)
throw new InvalidCastException();
return _chunks[0] != 0;
}
public bool ToBoolean(IFormatProvider provider)
{
return ToBoolean();
}
char IConvertible.ToChar(IFormatProvider provider)
{
//To a char in what character encoding?
//If we insist on UTF-8 as a reasonable choice for most modern code, what
//do we do with surrogate pairs?
//
//In all, there's no single reasonable approach to this.
throw new NotSupportedException();
}
[CLSCompliant(false)]
public sbyte ToSByte()
{
return (sbyte)ToByte();
}
[CLSCompliant(false)]
public sbyte ToSByte(IFormatProvider provider)
{
return ToSByte();
}
public byte ToByte()
{
if(_lastChunkLen > 8)
throw new InvalidCastException();
return (byte)(ToUInt32() & 0x000000FFu);
}
public byte ToByte(IFormatProvider provider)
{
return ToByte();
}
public short ToInt16()
{
return (short)ToUInt16();
}
public short ToInt16(IFormatProvider provider)
{
return ToInt16();
}
[CLSCompliant(false)]
public ushort ToUInt16()
{
if(_lastChunkLen > 16)
throw new InvalidCastException();
return (ushort)(ToUInt32() & 0x0000FFFFu);
}
[CLSCompliant(false)]
public ushort ToUInt16(IFormatProvider provider)
{
return ToUInt16();
}
public int ToInt32()
{
return (int)ToUInt32();
}
public int ToInt32(IFormatProvider provider)
{
return ToInt32();
}
[CLSCompliant(false)]
public uint ToUInt32()
{
switch(_chunks.Count)
{
case 0:
return 0;
case 1:
return _chunks[0] >> - _lastChunkLen;
default:
throw new InvalidCastException();
}
}
[CLSCompliant(false)]
public uint ToUInt32(IFormatProvider provider)
{
return ToUInt32();
}
public long ToInt64()
{
return (long)ToUInt64();
}
public long ToInt64(IFormatProvider provider)
{
return ToInt64();
}
[CLSCompliant(false)]
public ulong ToUInt64()
{
switch(_chunks.Count)
{
case 0: case 1:
return ToUInt32();
case 2:
return (((ulong)_chunks[0]) << 32 | (ulong)_chunks[1]) >> 32 -_lastChunkLen;
default:
throw new InvalidCastException();
}
}
[CLSCompliant(false)]
public ulong ToUInt64(IFormatProvider provider)
{
return ToUInt64();
}
float IConvertible.ToSingle(IFormatProvider provider)
{
throw new NotSupportedException();
}
double IConvertible.ToDouble(IFormatProvider provider)
{
throw new NotSupportedException();
}
decimal IConvertible.ToDecimal(IFormatProvider provider)
{
throw new NotSupportedException();
}
DateTime IConvertible.ToDateTime(IFormatProvider provider)
{
throw new NotSupportedException();
}
public string ToString(IFormatProvider provider)
{
return ToString();
}
///
/// Interprets the bitstring as a series of bits in an encoded character string,
/// encoded according to the Encoding passed, and returns that string.
/// The bitstring must contain a whole number of octets(bytes) and also be
/// valid according to the Encoding passed.
///
/// The to use in producing the string.
/// The string that was encoded in the BitString.
public string ToString(Encoding encoding)
{
return encoding.GetString(new List(ToByteEnumerable()).ToArray());
}
///
/// Interprets the bitstring as a series of octets (bytes) and returns those octets. Fails
/// if the Bitstring does not contain a whole number of octets (its length is not evenly
/// divisible by 8).
///
public IEnumerable ToByteEnumerable()
{
if(_lastChunkLen % 8 != 0)
throw new InvalidCastException();
foreach(uint chunk in EnumChunks(_lastChunkLen == 0))
for(int i = 24; i != -8; i -= 8)
yield return (byte)(chunk >> i & 0x000000FFu);
for(int i = 24; i > 24 - _lastChunkLen; i -=8)
yield return (byte)(_chunks[_chunks.Count - 1] >> i);
}
///
/// Interprets the bitstring as a series of signed octets (bytes) and returns those octets. Fails
/// if the Bitstring does not contain a whole number of octets (its length is not evenly
/// divisible by 8).
/// This method is not CLS-Compliant and may not be available to languages that cannot
/// handle signed bytes.
///
[CLSCompliant(false)]
public IEnumerable ToSByteEnumerable()
{
foreach(byte b in ToByteEnumerable())
yield return (sbyte)b;
}
///
/// Interprets the bitstring as a series of unsigned 16-bit integers and returns those integers.
/// Fails if the Bitstring's length is not evenly divisible by 16.
/// This method is not CLS-Compliant and may not be available to languages that cannot
/// handle unsigned integers.
///
[CLSCompliant(false)]
public IEnumerable ToUInt16Enumerable()
{
if(_lastChunkLen % 16 != 0)
throw new InvalidCastException();
foreach(uint chunk in EnumChunks(_lastChunkLen == 0))
{
yield return (ushort)(chunk >> 16);
yield return (ushort)(chunk & 0xFFFF);
}
if(_lastChunkLen == 16)
yield return (ushort)(_chunks[_chunks.Count] >> 16);
}
///
/// Interprets the bitstring as a series of 16-bit integers and returns those integers.
/// Fails if the Bitstring's length is not evenly divisible by 16.
///
public IEnumerable ToInt16Enumerable()
{
foreach(ushort us in ToUInt16Enumerable())
yield return (short)us;
}
///
/// Interprets the bitstring as a series of unsigned 32-bit integers and returns those integers.
/// Fails if the Bitstring's length is not evenly divisible by 32.
/// This method is not CLS-Compliant and may not be available to languages that cannot
/// handle unsigned integers.
///
[CLSCompliant(false)]
public IEnumerable ToUInt32Enumerable()
{
if(_lastChunkLen != 0)
throw new InvalidCastException();
return _chunks;
}
///
/// Interprets the bitstring as a series of signed 32-bit integers and returns those integers.
/// Fails if the Bitstring's length is not evenly divisible by 32.
///
public IEnumerable ToInt32Enumerable()
{
foreach(uint ui in ToUInt32Enumerable())
yield return (int)ui;
}
///
/// Interprets the bitstring as a series of unsigned 64-bit integers and returns those integers.
/// Fails if the Bitstring's length is not evenly divisible by 64.
/// This method is not CLS-Compliant and may not be available to languages that cannot
/// handle unsigned integers.
///
[CLSCompliant(false)]
public IEnumerable ToUInt64Enumerable()
{
if(_lastChunkLen != 0 || _chunks.Count % 2 != 0)
throw new InvalidCastException();
for(int i = 0; i != _chunks.Count; i += 2)
yield return (ulong)_chunks[i] << 32 | (ulong)_chunks[i + 1];
}
///
/// Interprets the bitstring as a series of signed 64-bit integers and returns those integers.
/// Fails if the Bitstring's length is not evenly divisible by 64.
///
public IEnumerable ToInt64Enumerable()
{
foreach(ulong ul in ToUInt64Enumerable())
yield return (long)ul;
}
public object ToType(Type conversionType, IFormatProvider provider)
{
switch(Type.GetTypeCode(conversionType))
{
case TypeCode.Boolean:
return ToBoolean();
case TypeCode.Byte:
return ToByte();
case TypeCode.DBNull:
return System.DBNull.Value;
case TypeCode.Empty:
return null;
case TypeCode.Int16:
return ToInt16();
case TypeCode.Int32:
return ToInt32();
case TypeCode.Int64:
return ToInt64();
case TypeCode.SByte:
return ToSByte();
case TypeCode.String:
return ToString();
case TypeCode.UInt16:
return ToUInt16();
case TypeCode.UInt32:
return ToUInt32();
case TypeCode.UInt64:
return ToUInt64();
case TypeCode.Object:
if(conversionType == typeof(BitString))
return this;
//Should fill this out a bit to cover the IEnumerable interfaces.
throw new InvalidCastException();
default:
throw new InvalidCastException();
}
}
}
}