Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite the data access using a pointer instead of popping from array #10

Open
MarkJeronimus opened this issue Nov 8, 2018 · 3 comments

Comments

@MarkJeronimus
Copy link

MarkJeronimus commented Nov 8, 2018

I noticed the parser pops data from the array, effectively consuming it. This blocks #9 and in turn #8.

I suggest rewriting it to a bit-container with random-access and a walking 'current pointer'.

Here's my implementation in Java that you should be able to cannibalize.

public final class BitContainer {
	private final byte[] buffer;
	private final int    bitCapacity;

	private int pointer       = 0; // byte pointer
	private int remainingBits = 8; // Always in the range [1, 8]

	private BitContainer(byte[] buffer, int bitCapacity) {
		this.buffer = requireNonNull(buffer, "buffer");
		this.bitCapacity = bitCapacity;
	}

	public static BitContainer allocate(int bitCapacity) {
		requireAtLeast(1, bitCapacity, "bitCapacity");
		int    capacity = (bitCapacity + 7) / 8;
		byte[] buffer   = new byte[capacity];
		return new BitContainer(buffer, bitCapacity);
	}

	public int bitCapacity()    { return bitCapacity; }

	public int capacity()       { return (bitCapacity + 7) / 8; }

	public int remainingBits() { return bitCapacity - buffer.position() * 8 + remainingBits - 8; }

	public void setPointer(int bitPosition) {
		pointer = bitPosition / 8;
		remainingBits = 8 - (bitPosition & 7);
	}

	public byte[] getArray(int numBits) {
		requireThat((numBits & 7) == 0, "numBits not an integral number of bytes: " + numBits);
		if (numBits > remainingBits())
			throw new IndexOutOfBoundsException(numBits + " > " + remainingBits());

		byte[] array = new byte[numBits / 8];

		// Use more efficient method for byte-aligned bytes
		if (remainingBits == 8) {
			for (int i = 0; i < array.length; i++)
				array[i] = buffer[pointer++];
		} else {
			for (int i = 0; i < array.length; i++)
				array[i] = (byte)getBits(8);
		}

		return array;
	}

	public void putArray(byte[] array) {
		requireNonNull(array, "array");
		if (array.length * 8 > remainingBits())
			throw new IndexOutOfBoundsException(array.length * 8 + " > " + remainingBits());

		// Use more efficient method for byte-aligned bytes
		if (remainingBits == 8) {
			for (byte element : array)
				buffer[pointer++] = element;
		} else {
			for (byte element : array)
				putBits(8, element);
		}
	}

	public int getBits(int numBits) {
		requireRange(1, 32, numBits, "numBits");
		if (numBits > remainingBits())
			throw new IndexOutOfBoundsException(numBits + " > " + remainingBits());

		if (numBits < remainingBits) {
			// Get middle bits of this byte and don't advance byte pointer
			byte value  = buffer[pointer];
			int  result = extractBits(value, remainingBits - numBits, numBits);
			remainingBits -= numBits;
			return result;
		}

		int result;
		if (remainingBits != 8) {
			// Get all remaining bits of this byte and advance byte pointer
			byte value = buffer[pointer++];
			result = extractBits(value, 0, remainingBits);
			numBits -= remainingBits;
		} else {
			result = 0;
		}

		// Get middle bytes and advance byte pointer
		while (numBits >= 8) {
			result <<= 8;
			result |= buffer[pointer++] & 0xFF;
			numBits -= 8;
		}

		if (numBits > 0) {
			// Get remaining bits of this byte and don't advance byte pointer
			byte value = buffer[pointer];
			result <<= numBits;
			result |= extractBits(value, 8 - numBits, numBits);
		}

		remainingBits = 8 - numBits;

		return result;
	}

	public void putBits(int numBits, int bits) {
		requireRange(1, 32, numBits, "numBits");
		if (numBits > remainingBits())
			throw new IndexOutOfBoundsException(numBits + " > " + remainingBits());

		if (numBits < remainingBits) {
			// Put all bits in the middle of this byte and don't advance byte pointer
			byte value  = buffer[pointer];
			byte result = injectBits(bits, value, remainingBits - numBits, numBits);
			buffer[pointer] = result;
			remainingBits -= numBits;
			return;
		}

		if (remainingBits != 8) {
			// Fill remaining bits of this byte and advance byte pointer
			byte value    = buffer[pointer];
			int  highBits = extractBits(bits, numBits - remainingBits, remainingBits);
			byte result   = injectBits(highBits, value, 0, remainingBits);
			numBits -= remainingBits;
			buffer[pointer++] = result;
		}

		// Fill middle bytes and advance byte pointer
		while (numBits >= 8) {
			int value = extractBits(bits, numBits - 8, 8);
			buffer[pointer++] = (byte)value;
			numBits -= 8;
		}

		if (numBits > 0) {
			// Put remaining bits in this byte and don't advance byte pointer
			byte value   = buffer[pointer];
			int  lowBits = extractBits(bits, 0, numBits);
			byte result  = injectBits(lowBits, value, 8 - numBits, numBits);
			buffer[pointer] = result;
		}

		remainingBits = 8 - numBits;
	}

	private static int extractBits(byte value, int firstBit, int numBits) {
		int mask = (1 << numBits) - 1;
		return value >> firstBit & mask;
	}

	private static int extractBits(int value, int firstBit, int numBits) {
		int mask = (1 << numBits) - 1;
		return value >> firstBit & mask;
	}

	private static byte injectBits(int bits, byte value, int firstBit, int numBits) {
		int mask = ((1 << numBits) - 1) << firstBit;
		return (byte)((value & ~mask) | (bits << firstBit & mask));
	}
}
@zuckschwerdt
Copy link
Member

Yes, I'll split out all the helpers to a proper bitbuffer and then put something like this on top.

@MarkJeronimus
Copy link
Author

Bug fixed in setPointer(). And remember in Java / doesn't promote a type to float, it truncates.

@MarkJeronimus
Copy link
Author

Good thing I posted it here; I keep finding previously unnoticed bugs. (first post updated)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants