// TODO: Safety check that functions taking byte length can't overflow bit conversion (log2(num_bytes) > 61) //////////////////////////////////////////////////////////// //~ Buff management BB_Buff BB_AcquireDynamicBuff(u64 arena_reserve) { BB_Buff result = Zi; result.arena = AcquireArena(arena_reserve); result.is_backed_by_arena = 1; return result; } void BB_ReleaseDynamicBuff(BB_Buff *bb) { // Only arena bitbuffs need to be released if (bb->is_backed_by_arena) { ReleaseArena(bb->arena); } } BB_Buff BB_BuffFromString(String s) { BB_Buff result = Zi; result.fixed_buffer = s; return result; } //////////////////////////////////////////////////////////// //~ Writer management BB_Writer BB_WriterFromBuff(BB_Buff *bb) { BB_Writer result = Zi; result.bb = bb; if (bb->is_backed_by_arena) { result.base = ArenaFirst(bb->arena, u8); } else { result.base = bb->fixed_buffer.text; } result.cur_bit = 0; #if BITBUFF_DEBUG result.debug_enabled = 1; #endif return result; } // Use this when writing external formats that will not verify bitbuff debug symbols / magic numbers BB_Writer BB_WriterFromBuffNoDebug(BB_Buff *bb) { BB_Writer result = BB_WriterFromBuff(bb); #if BITBUFF_DEBUG result.debug_enabled = 0; #endif return result; } void BB_ResetWriter(BB_Writer *bbw) { bbw->overflowed = 0; bbw->cur_bit = 0; if (bbw->bb->is_backed_by_arena) { ResetArena(bbw->bb->arena); } } // FIXME: Handle overflowed bbw u64 BB_GetNumBitsWritten(BB_Writer *bbw) { return bbw->cur_bit; } // FIXME: Handle overflowed bbw u64 BB_GetNumBytesWritten(BB_Writer *bbw) { return (bbw->cur_bit + 7) >> 3; } // FIXME: Handle overflowed bbw String BB_GetWritten(Arena *arena, BB_Writer *bbw) { String result = Zi; result.len = (bbw->cur_bit + 7) >> 3; result.text = PushStructsNoZero(arena, u8, result.len); CopyBytes(result.text, bbw->base, result.len); return result; } // FIXME: Handle overflowed bbw u8 *BB_GetWrittenRaw(BB_Writer *bbw) { return bbw->base; } // Returns 1 if num_bits would cause the writer to overflow its fixed buffer size (if writer is not backed by a dynamic arena bitbuff) b32 BB_CheckWriterOverflowBits(BB_Writer *bbw, u64 num_bits) { b32 result = 0; BB_Buff *bb = bbw->bb; if (bbw->overflowed) { result = 1; } else { u64 bytes_needed = (bbw->cur_bit + num_bits + 7) >> 3; if (bb->is_backed_by_arena) { Arena *arena = bb->arena; if (bytes_needed >= arena->pos) { // Grow arena u64 push_size = (((bytes_needed - arena->pos) / BB_WriterOverflowArenaPushSize) + 1) * BB_WriterOverflowArenaPushSize; PushStructsNoZero(arena, u8, push_size); } } else { u64 max_len = bb->fixed_buffer.len; if (bytes_needed > max_len) { // Writer overflowed fixed buffer #if BITBUFF_DEBUG Assert(0); #endif result = 1; bbw->cur_bit = max_len << 3; bbw->overflowed = 1; } } } return result; } //////////////////////////////////////////////////////////// //~ Align writer // Align the pos to the next byte void BB_WriteAlignToNextByte(BB_Writer *bbw) { #if BITBUFF_DEBUG if ((bbw->cur_bit & 7) != 0) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_AlignNextByte, 0); } #endif bbw->cur_bit += (8 - (bbw->cur_bit & 7)) & 7; } void BB_WriteAlignBytes(BB_Writer *bbw, u64 align) { BB_WriteAlignToNextByte(bbw); BB_WriteDebugMagic(bbw, BB_DebugMagicKind_AlignBytes, align); if (align > 0) { u64 new_pos = (bbw->cur_bit >> 3); new_pos += (align - 1); new_pos -= new_pos % align; if (BB_CheckWriterOverflowBits(bbw, (new_pos << 3) - bbw->cur_bit)) { return; } bbw->cur_bit = new_pos << 3; } } //////////////////////////////////////////////////////////// //~ Write bits void BB_WriteUBitsNoMagic(BB_Writer *bbw, u64 value, u8 num_bits) { Assert(num_bits > 0 && (num_bits == 64 || value <= ~(U64Max << num_bits))); // Bit count must be able to hold value if (BB_CheckWriterOverflowBits(bbw, num_bits)) { return; } u8 offset = bbw->cur_bit & 7; if (offset != 0) { // Write unaligned bits u8 *at = bbw->base + (bbw->cur_bit >> 3); u8 num_mix_bits = MinU8((8 - offset), num_bits); u8 mix_byte = (u8)((value & ((1 << num_mix_bits) - 1)) << offset); *at |= mix_byte; value >>= num_mix_bits; num_bits -= num_mix_bits; bbw->cur_bit += num_mix_bits; } // cur_bit is now aligned to byte u8 *at = bbw->base + (bbw->cur_bit >> 3); u8 num_bytes = (num_bits + 7) >> 3; CopyBytes(at, &value, num_bytes); bbw->cur_bit += num_bits; } void BB_WriteUBits(BB_Writer *bbw, u64 value, u8 num_bits) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_UBits, num_bits); BB_WriteUBitsNoMagic(bbw, value, num_bits); } void BB_WriteIBits(BB_Writer *bbw, i64 value, u8 num_bits) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_IBits, num_bits); u64 ubits; if (value >= 0) { ubits = value; } else { ubits = BB_TwosComplimentFromUint(-value, num_bits); } BB_WriteUBits(bbw, ubits, num_bits); } // Returns written bit to make writing delta encoding logic cleaner b32 BB_WriteBit(BB_Writer *bbw, u8 value) { BB_WriteUBits(bbw, value, 1); return value; } //////////////////////////////////////////////////////////// //~ Write variable length integers // Writes a variable length unsigned integer. // Value is written in chunks of 7 bits w/ 8th bit signaling continuation. void BB_WriteUV(BB_Writer *bbw, u64 value) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_UV, 0); while (value > 0x7F) { u8 cont_byte = 0x80 | (value & 0x7F); BB_WriteUBits(bbw, cont_byte, 8); value >>= 7; } BB_WriteUBits(bbw, value, 8); } // Writes a variable length signed integer. // Similar to BB_WriteUV, except the 7th bit of the first byte is a sign bit // indicating that the value is stored in twos compliment. void BB_WriteIV(BB_Writer *bbw, i64 value) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_IV, 0); u8 sign_bit; u64 tc; if (value >= 0) { sign_bit = 0; tc = value; } else { sign_bit = 1; u64 unsigned_value = -value; u8 num_bits = 6; unsigned_value >>= 6; while (unsigned_value > 0) { num_bits += 7; unsigned_value >>= 7; } num_bits = MinU8(num_bits, 64); tc = BB_TwosComplimentFromUint(-value, num_bits); } // First byte contains not just cont bit, but sign bit as well. u8 first_byte = (tc & 0x3F); tc >>= 6; first_byte |= (tc > 0) << 7; // Cont bit first_byte |= sign_bit << 6; // Sign bit BB_WriteUBits(bbw, first_byte, 8); if (tc > 0) { while (tc > 0x7F) { u8 cont_byte = 0x80 | (tc & 0x7F); BB_WriteUBits(bbw, cont_byte, 8); tc >>= 7; } BB_WriteUBits(bbw, tc, 8); } } //////////////////////////////////////////////////////////// //~ Write floating point void BB_WriteF32(BB_Writer *bbw, f32 value) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_F32, 0); BB_WriteUBits(bbw, *(u32 *)&value, 32); } void BB_WriteF64(BB_Writer *bbw, f64 value) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_F64, 0); BB_WriteUBits(bbw, *(u64 *)&value, 64); } //////////////////////////////////////////////////////////// //~ Write Uid void BB_WriteUid(BB_Writer *bbw, Uid value) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_Uid, 128); BB_WriteUBits(bbw, value.hi, 64); BB_WriteUBits(bbw, value.lo, 64); } //////////////////////////////////////////////////////////// //~ Write raw data void BB_WriteString(BB_Writer *bbw, String s) { BB_WriteDebugMagic(bbw, BB_DebugMagicKind_String, 0); BB_WriteUV(bbw, s.len); BB_WriteBytes(bbw, s); } void BB_WriteBytes(BB_Writer *bbw, String bytes) { // Align start of bytes BB_WriteAlignToNextByte(bbw); u64 num_bits = bytes.len << 3; if (BB_CheckWriterOverflowBits(bbw, num_bits)) { return; } u8 *at = bbw->base + (bbw->cur_bit >> 3); CopyBytes(at, bytes.text, bytes.len); bbw->cur_bit += num_bits; } void BB_WriteSeekBytes(BB_Writer *bbw, u64 num_bytes) { BB_WriteAlignToNextByte(bbw); u64 num_bits = num_bytes << 3; if (BB_CheckWriterOverflowBits(bbw, num_bits)) { return; } bbw->cur_bit += num_bits; } //////////////////////////////////////////////////////////// //~ Writer debug #if BITBUFF_DEBUG void BB_WriteDebugMarker(BB_Writer *bbw, String name) { bbw->cur_bit += (8 - (bbw->cur_bit & 7)) & 7; for (u64 i = 0; i < name.len; ++i) { BB_WriteUBitsNoMagic(bbw, name.text[i], 8); } } void BB_WriteDebugMagic(BB_Writer *bbw, BB_DebugMagicKind magic, u8 num_bits) { if (bbw->debug_enabled) { if (BB_CheckWriterOverflowBits(bbw, 24)) { return; } u64 magic_ubits = (u64)magic | ((u64)num_bits << 16); BB_WriteUBitsNoMagic(bbw, magic_ubits, 24); } } #endif //////////////////////////////////////////////////////////// //~ Reader management BB_Reader BB_ReaderFromBuff(BB_Buff *bb) { BB_Reader result = Zi; if (!bb->is_backed_by_arena) { result.base = bb->fixed_buffer.text; result.base_len = bb->fixed_buffer.len; } else { Arena *arena = bb->arena; result.base = ArenaFirst(arena, u8); result.base_len = arena->pos; } result.cur_bit = 0; #if BITBUFF_DEBUG result.debug_enabled = 1; #endif return result; } // Use this when reading from external formats that will not contain bitbuff debug symbols / magic numbers BB_Reader BB_ReaderFromBuffNoDebug(BB_Buff *bb) { BB_Reader result = BB_ReaderFromBuff(bb); #if BITBUFF_DEBUG result.debug_enabled = 0; #endif return result; } // Returns the number of bits read from the bitbuff // FIXME: Handle overflowed bbr u64 BB_GetCurrentReaderBit(BB_Reader *bbr) { return bbr->cur_bit; } // Returns the number of *full* bytes read from the bitbuff // FIXME: Handle overflowed bbr u64 BB_GetCurrentReaderByte(BB_Reader *bbr) { return bbr->cur_bit >> 3; } // Returns the number of bits left until the bitbuff overflows // FIXME: Handle overflowed bbr u64 BB_NumBitsRemaining(BB_Reader *bbr) { return (bbr->base_len << 3) - bbr->cur_bit; } // Returns the number of *full* bytes left until the bitbuff overflows // FIXME: Handle overflowed bbr u64 BB_NumBytesRemaining(BB_Reader *bbr) { return bbr->base_len - (bbr->cur_bit >> 3); } b32 BB_CheckReaderOverflowBits(BB_Reader *bbr, u64 num_bits) { b32 result = 0; if (bbr->overflowed) { result = 1; } else { u64 bits_needed = bbr->cur_bit + num_bits; u64 base_len_bits = bbr->base_len << 3; if (bits_needed > base_len_bits) { // Tried to read past bitbuff memory #if BITBUFF_DEBUG Assert(0); #endif result = 1; bbr->cur_bit = base_len_bits; bbr->overflowed = 1; } } return result; } //////////////////////////////////////////////////////////// //~ Align reader // Align the pos to the next byte void BB_ReadAlignToNextByte(BB_Reader *bbr) { #if BITBUFF_DEBUG if ((bbr->cur_bit & 7) != 0) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_AlignNextByte, 0); } #endif bbr->cur_bit += (8 - (bbr->cur_bit & 7)) & 7; } void BB_ReadAlignBytes(BB_Reader *bbr, u64 align) { BB_ReadAlignToNextByte(bbr); BB_ReadDebugMagic(bbr, BB_DebugMagicKind_AlignBytes, align); if (align > 0) { u64 new_pos = (bbr->cur_bit >> 3); new_pos += (align - 1); new_pos -= new_pos % align; if (BB_CheckReaderOverflowBits(bbr, (new_pos << 3) - bbr->cur_bit)) { return; } bbr->cur_bit = new_pos << 3; } } //////////////////////////////////////////////////////////// //~ Read bits u64 BB_ReadUBitsNoMagic(BB_Reader *bbr, u8 num_bits) { if (BB_CheckReaderOverflowBits(bbr, num_bits)) { return 0; } u64 result = 0; u8 offset = bbr->cur_bit & 7; u8 num_trailing_bits = 0; if (offset) { u8 *at = bbr->base + (bbr->cur_bit >> 3); num_trailing_bits = MinU8(8 - offset, num_bits); u8 mix_byte = *at; mix_byte >>= offset; mix_byte &= (1 << num_trailing_bits) - 1; result = mix_byte; num_bits -= num_trailing_bits; bbr->cur_bit += num_trailing_bits; } // cur_bit is now aligned to byte u8 *at = bbr->base + (bbr->cur_bit >> 3); u8 num_bytes = (num_bits + 7) >> 3; u64 tmp = 0; CopyBytes(&tmp, at, num_bytes); u64 mask = U64Max; if (num_bits < 64) { mask = ~(U64Max << num_bits); } tmp &= mask; result |= tmp << num_trailing_bits; bbr->cur_bit += num_bits; return result; } u64 BB_ReadUBits(BB_Reader *bbr, u8 num_bits) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_UBits, num_bits); return BB_ReadUBitsNoMagic(bbr, num_bits); } i64 BB_ReadIBits(BB_Reader *bbr, u8 num_bits) { Assert(num_bits > 1); BB_ReadDebugMagic(bbr, BB_DebugMagicKind_IBits, num_bits); u64 tc = BB_ReadUBits(bbr, num_bits); return BB_IntFromTwosCompliment(tc, num_bits); } u8 BB_ReadBit(BB_Reader *bbr) { return BB_ReadUBits(bbr, 1); } //////////////////////////////////////////////////////////// //~ Read variable length integer // Read a variable length unsigned integer. // See BB_WriteUV for details. u64 BB_ReadUV(BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_UV, 0); u64 result = 0; for (u64 i = 0; i <= 9; ++i) { u64 part = BB_ReadUBits(bbr, 8); u8 is_last_part = part <= 0x7F; result |= (part & 0x7F) << (i * 7); if (is_last_part) { break; } } return result; } // Read a variable length signed integer. // See BB_WriteIV for details. i64 BB_ReadIV(BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_IV, 0); u8 first_byte = BB_ReadUBits(bbr, 8); u8 cont_bit = first_byte & 0x80; u8 sign_bit = first_byte & 0x40; u8 num_bits = 6; u64 tc = first_byte & 0x3F; if (cont_bit) { for (u64 i = 0; i <= 9; ++i) { u64 part = BB_ReadUBits(bbr, 8); u8 is_last_part = part <= 0x7F; tc |= (part & 0x7F) << num_bits; num_bits += 7; if (is_last_part) { break; } } } num_bits = MinU8(num_bits, 64); i64 result; if (sign_bit) { // Sign bit is 1, indicating result is stored in twos compliment result = BB_IntFromTwosCompliment(tc, num_bits); } else { result = (i64)tc; } return result; } //////////////////////////////////////////////////////////// //~ Read floating point f32 BB_ReadF32(BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_F32, 0); u32 ubits = BB_ReadUBits(bbr, 32); return *(f32 *)&ubits; } f64 BB_ReadF64(BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_F64, 0); u64 ubits = BB_ReadUBits(bbr, 64); return *(f64 *)&ubits; } //////////////////////////////////////////////////////////// //~ Read Uid Uid BB_ReadUid(BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_Uid, 128); u64 hi = BB_ReadUBits(bbr, 64); u64 lo = BB_ReadUBits(bbr, 64); return UID(hi, lo); } //////////////////////////////////////////////////////////// //~ Read raw data String BB_ReadString(Arena *arena, BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_String, 0); String result = Zi; u64 len = BB_ReadUV(bbr); u8 *src = BB_ReadBytesRaw(bbr, len); if (src != 0) { result.len = len; result.text = PushStructsNoZero(arena, u8, len); CopyBytes(result.text, src, len); } return result; } String BB_ReadStringRaw(BB_Reader *bbr) { BB_ReadDebugMagic(bbr, BB_DebugMagicKind_String, 0); String result = Zi; u64 len = BB_ReadUV(bbr); u8 *src = BB_ReadBytesRaw(bbr, len); if (src != 0) { result.len = len; result.text = src; } return result; } // Will fill dst with zeroes if bitbuff overflows void BB_ReadBytes(BB_Reader *bbr, String out) { u8 *src = BB_ReadBytesRaw(bbr, out.len); if (src) { CopyBytes(out.text, src, out.len); } else { ZeroBytes(out.text, out.len); } } // Will return 0 on bitbuff overflow. Result should be checked. u8 *BB_ReadBytesRaw(BB_Reader *bbr, u64 num_bytes) { BB_ReadAlignToNextByte(bbr); u64 num_bits = num_bytes << 3; if (BB_CheckReaderOverflowBits(bbr, num_bits)) { return 0; } u8 *raw = bbr->base + (bbr->cur_bit >> 3); bbr->cur_bit += num_bits; return raw; } void BB_ReadSeekBytes(BB_Reader *bbr, u64 num_bytes) { BB_ReadAlignToNextByte(bbr); u64 num_bits = num_bytes << 3; if (BB_CheckReaderOverflowBits(bbr, num_bits)) { return; } bbr->cur_bit += num_bits; } void BB_ReadSeekToByte(BB_Reader *bbr, u64 pos) { u64 cur_byte_pos = bbr->cur_bit >> 3; if (pos >= cur_byte_pos) { BB_ReadSeekBytes(bbr, pos - cur_byte_pos); } else { // Tried to seek byte backwards in reader Assert(0); bbr->overflowed = 1; bbr->cur_bit = (bbr->base_len << 3); } } //////////////////////////////////////////////////////////// //~ Reader debug #if BITBUFF_DEBUG void BB_ReadDebugMagic(BB_Reader *bbr, BB_DebugMagicKind expected_magic, u8 expected_num_bits) { if (bbr->debug_enabled) { if (BB_CheckReaderOverflowBits(bbr, 24)) { return; } u64 stored = BB_ReadUBitsNoMagic(bbr, 24); BB_DebugMagicKind stored_magic = stored & 0xFFFF; u8 stored_num_bits = (stored >> 16) & 0xFF; // Verify stored magic match Assert(expected_magic == stored_magic); // Verify stored bit count match Assert(expected_num_bits == stored_num_bits); } } void BB_ReadDebugMarker(BB_Reader *bbr, String name) { bbr->cur_bit += (8 - (bbr->cur_bit & 7)) & 7; for (u64 i = 0; i < name.len; ++i) { u8 c_stored = BB_ReadUBitsNoMagic(bbr, 8); u8 c_expected = name.text[i]; Assert(c_expected == c_stored); } } #endif //////////////////////////////////////////////////////////// //~ Utils u64 BB_TwosComplimentFromUint(u64 value, u8 num_bits) { u64 mask = U64Max; if (num_bits < 64) { mask = ~(U64Max << num_bits); } u64 tc = (~value & mask) + 1; tc &= mask; return tc; } i64 BB_IntFromTwosCompliment(u64 tc, u8 num_bits) { u64 msb_mask = (u64)1 << (num_bits - 1); i64 value = -(i64)(tc & msb_mask); value += tc & ~msb_mask; return value; } //////////////////////////////////////////////////////////// //~ Test void BB_Test(void) { TempArena scratch = BeginScratchNoConflict(); u8 kind_ubits = 0; u8 kind_ibits = 1; u8 kind_uv = 2; u8 kind_iv = 3; u8 kind_string = 4; struct test_case_ubits { u64 v; u64 num_bits; }; struct test_case_ibits { i64 v; u64 num_bits; }; struct test_case_uv { u64 v; }; struct test_case_iv { i64 v; }; struct test_case_string { String v; }; struct test_case { u8 kind; union { struct test_case_ubits ubits; struct test_case_ibits ibits; struct test_case_uv uv; struct test_case_iv iv; struct test_case_string s; }; }; struct test_case cases[] = { { kind_ubits, .ubits = { 40, 8 } }, { kind_ubits, .ubits = { 32, 8 } }, { kind_ubits, .ubits = { 100, 7 } }, { kind_ubits, .ubits = { 4, 3 } }, { kind_ubits, .ubits = { 13, 8 } }, { kind_ibits, .ibits = { 0, 8 } }, { kind_ibits, .ibits = { -1, 8 } }, { kind_ibits, .ibits = { -2, 8 } }, { kind_ibits, .ibits = { -3, 8 } }, { kind_ibits, .ibits = { -100, 8 } }, { kind_ibits, .ibits = { -50, 7 } }, { kind_ibits, .ibits = { 50, 7 } }, { kind_ibits, .ibits = { 4, 7 } }, { kind_ibits, .ibits = { 1, 7 } }, { kind_ibits, .ibits = { 3, 3 } }, { kind_ibits, .ibits = { 1, 2 } }, { kind_ibits, .ibits = { 0, 2 } }, { kind_ibits, .ibits = { -1, 2 } }, { kind_uv, .uv = { 0 } }, { kind_uv, .uv = { 100 } }, { kind_uv, .uv = { 10000 } }, { kind_uv, .uv = { 10000000000000 } }, { kind_uv, .uv = { U64Max } }, { kind_iv, .iv = { 0 } }, { kind_iv, .iv = { -1 } }, { kind_iv, .iv = { 10000000000000 } }, { kind_iv, .iv = { -10000000000000 } }, { kind_iv, .iv = { I64Max } }, { kind_iv, .iv = { I64Min } }, { kind_string, .s = { Lit("Hello there! Hope you're doing well.") } }, { kind_ibits, .ibits = { 3, 3 } }, { kind_string, .s = { Lit("Alriiiiiiiiiiiiiiiiiiighty then") } }, { kind_string, .s = { Lit("Alriiiiiiiiiiiiiiiiiiighty then") } }, }; String encoded = Zi; { BB_Buff bb = BB_AcquireDynamicBuff(Gibi(64)); BB_Writer bbw = BB_WriterFromBuff(&bb); for (u64 i = 0; i < countof(cases); ++i) { struct test_case c = cases[i]; if (c.kind == kind_ubits) { BB_WriteUBits(&bbw, c.ubits.v, c.ubits.num_bits); } else if (c.kind == kind_ibits) { BB_WriteIBits(&bbw, c.ibits.v, c.ibits.num_bits); } else if (c.kind == kind_uv) { BB_WriteUV(&bbw, c.uv.v); } else if (c.kind == kind_iv) { BB_WriteIV(&bbw, c.iv.v); } else if (c.kind == kind_string) { BB_WriteString(&bbw, c.s.v); } else { Assert(0); } } encoded = BB_GetWritten(scratch.arena, &bbw); } { BB_Buff bb = BB_BuffFromString(encoded); BB_Reader bbr = BB_ReaderFromBuff(&bb); for (u64 i = 0; i < countof(cases); ++i) { struct test_case c = cases[i]; if (c.kind == kind_ubits) { u64 w = c.ubits.v; u64 r = BB_ReadUBits(&bbr, c.ubits.num_bits); Assert(r == w); } else if (c.kind == kind_ibits) { i64 w = c.ibits.v; i64 r = BB_ReadIBits(&bbr, c.ubits.num_bits); Assert(r == w); } else if (c.kind == kind_uv) { u64 w = c.uv.v; u64 r = BB_ReadUV(&bbr); Assert(r == w); } else if (c.kind == kind_iv) { i64 w = c.iv.v; i64 r = BB_ReadIV(&bbr); Assert(r == w); } else if (c.kind == kind_string) { String w = c.s.v; String r = BB_ReadString(scratch.arena, &bbr); Assert(MatchString(r, w)); } else { Assert(0); } } } EndScratch(scratch); }