bitarray.js

/**
 * BitArray class implementing the JS `TypedArray` interface
 * for a binary array.
 */
class BitArray {
  /**
   * Constructor for `BitArray`
   *
   * This allows you to provide either an `ArrayBuffer` with pre-existing
   * data or a requested bit-size of the BitArray.
   *
   * @example <caption>Setting up BitArray with requested number of bits</caption>
   * const arr = new BitArray(32);
   *
   * @example <caption>Setting up BitArray from another ArrayBuffer</caption>
   * const u8_arr = new Uint8Array([255, 230]);
   * const arr = new BitArray(u8_arr.buffer);
   *
   * @memberOf BitArray
   * @param {ArrayBuffer|number} buf - Either an existing buffer containing data
   *                                   or a number indicating the size of the BitArray
   * @returns {BitArray}
   */
  constructor(buf) {
    if (!(buf instanceof ArrayBuffer) && typeof buf === 'number') {
      this.buffer = new ArrayBuffer(buf / 8);
      this.u8 = new Uint8Array(this.buffer);
    } else {
      this.buffer = buf;
      this.u8 = new Uint8Array(buf);
    }
    return new Proxy(this, {
      get(obj, prop) {
        if (!obj[prop]) {
          return obj.getBit(prop);
        } else {
          return obj[prop];
        }
      },
      set(obj, prop, value) {
        const propInt = parseInt(prop);
        if (isNaN(propInt)) {
          return false;
        } else {
          obj.setBit(prop, value);
          return true;
        }
      },
    });
  }

  /**
   * Wrapper around `BitArray.setArray` to ensure further
   * compliance to TypedArray interface.
   *
   * @param {ArrayLike|Array} array
   * @see {@link setArray} for further information.
   */
  set(array) {
    this.setArray(array);
  }

  /**
   * Get the bit (0 or 1) at bit index `idx`
   *
   * @example <caption>Getting a bit at index 0 with getBit</caption>
   * // returns 1
   * const u8_arr = new Uint8Array([255, 0]);
   * const arr = new BitArray(u8_arr.buffer);
   * arr.getBit(0)
   *
   * @example <caption>Getting a bit at index 0 with []</caption>
   * // returns 1
   * const u8_arr = new Uint8Array([255, 0]);
   * const arr = new BitArray(u8_arr.buffer);
   * arr[0]
   *
   * @memberOf BitArray
   * @param {number} idx - Bit index
   * @returns {number} 0 or 1 depending on if bit at `idx` is set
   */
  getBit(idx) {
    const v = this.u8[idx >> 3];
    const off = idx & 0x7;
    return (v >> (7 - off)) & 1;
  }

  /**
   * Size of `BitArray`
   *
   * @example <caption>Getting the length of BitArray</caption>
   * // returns 16
   * const u8_arr = new Uint8Array([255, 0]);
   * const arr = new BitArray(u8_arr.buffer);
   * arr.length
   *
   * @memberOf BitArray
   * @returns {number} The size of `BitArray` in number of bits
   */
  get length() {
    return this.u8.byteLength * 8;
  }

  /**
   * Set the bit at bit index `idx`
   *
   * Note: Non-zero `val` will set the bit to 1. 0 or undefined
   * `val` will set the bit to 0.
   *
   * @example <caption>Set the bit at index 0 to 1</caption>
   * const u8_arr = new Uint8Array([255, 0]);
   * const arr = new BitArray(u8_arr.buffer);
   * arr.setBit(0, 1);
   *
   * @memberOf BitArray
   * @param {number} idx - Bit index to set bit
   * @param {number?} val Value of bit to set (0 or 1)
   */
  setBit(idx, val) {
    const off = idx & 0x7;
    if (val) {
      this.u8[idx >> 3] |= 0x80 >> off;
    } else {
      this.u8[idx >> 3] &= ~(0x80 >> off);
    }
  }

  /**
   * Set the BitArray to `array`
   *
   * @example <caption>Set the BitArray to another binary array</caption>
   * const arr = new BitArray(4);
   * arr.setArray([1, 0, 0, 0);
   *
   * @memberOf BitArray
   * @param {ArrayLike|number[]|BitArray} array - An array of binary data
   */
  setArray(array) {
    // Iterate over `array.length` in case `.forEach`
    // hasn't been defined for `array`'s type
    const len = array.length;
    for (let i = 0; i < len; i++) {
      this.setBit(i, array[i]);
    }
  }

  /**
   * Returns the binary array from [start, stop)
   *
   * Note: if `start` and `stop` aren't provided,
   * this will return the entire array from [0, array.length].
   *
   * @example <caption>Get the subarray from 0 to 4</caption>
   * // returns [1, 1, 1, 1]
   * const u8_arr = new Uint8Array([255, 0]);
   * const arr = new BitArray(u8_arr);
   * arr.subarray(0, 4);
   *
   * @memberOf BitArray
   * @param {number?} start - Start bit index
   * @param {number?} stop - Stop bit index
   * @returns {number[]} Binary array in [`start`, `stop`)
   */
  subarray(start, stop) {
    let sub = [];
    start = start || 0;
    start = start < 0 ? 0 : start;
    stop = stop || this.length;
    stop = stop > this.length ? this.length : stop;
    for (let i = start; i < stop; i++) {
      sub.push(this.getBit(i));
    }
    return sub;
  }
}

export default BitArray;