Skip to content

Commit 3823482

Browse files
committed
Add readBitsInt{Be,Le}AsBigInt()
See kaitai-io/kaitai_struct#183 The compiler will use these `*AsBigInt()` methods to read bit-sized integers wider than 32 bits (i.e. starting with `b33`). The existing `readBitsInt{Be,Le}()` methods use the `Number` type, which limits bitwise operators to a width of 32 bits (hence the maximum supported width of 32 bits for these methods).
1 parent 0d25ee0 commit 3823482

1 file changed

Lines changed: 66 additions & 2 deletions

File tree

KaitaiStream.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,39 @@ class KaitaiStream {
645645
return res >>> 0;
646646
}
647647

648+
/**
649+
* @param n The number of bits to read.
650+
* @returns The read bits as a {@link BigInt}.
651+
*/
652+
public readBitsIntBeAsBigInt(n: number): bigint {
653+
let res = BigInt(0);
654+
655+
const bitsNeeded = n - this.bitsLeft;
656+
this.bitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8`
657+
658+
if (bitsNeeded > 0) {
659+
// 1 bit => 1 byte
660+
// 8 bits => 1 byte
661+
// 9 bits => 2 bytes
662+
const bytesNeeded = ((bitsNeeded - 1) >> 3) + 1; // `ceil(bitsNeeded / 8)` (NB: `x >> 3` is `floor(x / 8)`)
663+
const buf = this.mapUint8Array(bytesNeeded);
664+
for (let i = 0; i < bytesNeeded; i++) {
665+
res = res << BigInt(8) | BigInt(buf[i]);
666+
}
667+
668+
const newBits = Number(res & BigInt(0x7f));
669+
res = res >> BigInt(this.bitsLeft) | BigInt(this.bits) << BigInt(bitsNeeded);
670+
this.bits = newBits; // will be masked at the end of the function
671+
} else {
672+
res = BigInt(this.bits >>> -bitsNeeded); // shift unneeded bits out
673+
}
674+
675+
const mask = (1 << this.bitsLeft) - 1; // `bitsLeft` is in range 0..7
676+
this.bits &= mask;
677+
678+
return res;
679+
}
680+
648681
/**
649682
* Unused since Kaitai Struct Compiler v0.9+ - compatibility with older versions.
650683
*
@@ -675,8 +708,8 @@ class KaitaiStream {
675708
// 9 bits => 2 bytes
676709
const bytesNeeded = ((bitsNeeded - 1) >> 3) + 1; // `ceil(bitsNeeded / 8)` (NB: `x >> 3` is `floor(x / 8)`)
677710
const buf = this.mapUint8Array(bytesNeeded);
678-
for (let i = 0; i < bytesNeeded; i++) {
679-
res |= buf[i] << (i * 8);
711+
for (let i = bytesNeeded - 1; i >= 0; i--) {
712+
res = res << 8 | buf[i];
680713
}
681714

682715
// NB: in JavaScript, bit shift operators always shift by modulo 32 of the right-hand operand (see
@@ -702,6 +735,37 @@ class KaitaiStream {
702735
return res;
703736
}
704737

738+
/**
739+
* @param n The number of bits to read.
740+
* @returns The read bits as a {@link BigInt}.
741+
* @throws {RangeError}
742+
*/
743+
public readBitsIntLeAsBigInt(n: number): bigint {
744+
let res = BigInt(0);
745+
const bitsNeeded = n - this.bitsLeft;
746+
747+
if (bitsNeeded > 0) {
748+
// 1 bit => 1 byte
749+
// 8 bits => 1 byte
750+
// 9 bits => 2 bytes
751+
const bytesNeeded = ((bitsNeeded - 1) >> 3) + 1; // `ceil(bitsNeeded / 8)` (NB: `x >> 3` is `floor(x / 8)`)
752+
const buf = this.mapUint8Array(bytesNeeded);
753+
for (let i = bytesNeeded - 1; i >= 0; i--) {
754+
res = res << BigInt(8) | BigInt(buf[i]);
755+
}
756+
757+
const newBits = res >> BigInt(bitsNeeded);
758+
res = (res << BigInt(this.bitsLeft)) | BigInt(this.bits);
759+
this.bits = Number(newBits); // `newBits` is at most 7 bits wide => safe to convert
760+
} else {
761+
res = BigInt(this.bits);
762+
this.bits >>>= n;
763+
}
764+
765+
this.bitsLeft = -bitsNeeded & 7; // `-bitsNeeded mod 8`
766+
return BigInt.asUintN(n, res);
767+
}
768+
705769
/**
706770
* Native endianness. Either KaitaiStream.BIG_ENDIAN or KaitaiStream.LITTLE_ENDIAN
707771
* depending on the platform endianness.

0 commit comments

Comments
 (0)