From 361af0fe57d6600e9e054b227db482cbca7311cb Mon Sep 17 00:00:00 2001 From: Trinity Bee Date: Wed, 16 Sep 2026 08:39:43 +0000 Subject: [PATCH] Implement TriBitvector module with all 8 core functions - Implement empty(), with_capacity(), push(), pop(), get(), set(), len(), append() - Add comprehensive test blocks for all functions - Remove all TODO: Implement comments - Maintain clean parse with no recovery events - Add WORD_BITS constant for bit arithmetic Closes #3507 --- specs/tri/collections/bitvector.t27 | 174 ++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 22 deletions(-) diff --git a/specs/tri/collections/bitvector.t27 b/specs/tri/collections/bitvector.t27 index d0d801ca12..01d4e2830e 100644 --- a/specs/tri/collections/bitvector.t27 +++ b/specs/tri/collections/bitvector.t27 @@ -15,48 +15,96 @@ module TriBitvector; length : usize, }; + const WORD_BITS : usize = @bitSizeOf(usize); + // ═══════════════════════════════════════════════════════════ // 3. Core Functions // ═══════════════════════════════════════════════════════════ // empty() → BitVector fn empty() -> BitVector { - // TODO: Implement from .tri spec + return BitVector{ .bits = []usize{}, .length = 0 }; } - // with_capacity(bits: usize) → void - fn with_capacity(bits: usize) -> void { - // TODO: Implement from .tri spec + // with_capacity(bits: usize, allocator: std.mem.Allocator) → !BitVector + fn with_capacity(bits: usize, allocator: std.mem.Allocator) -> !BitVector { + const word_capacity = (bits + WORD_BITS - 1) / WORD_BITS; + const words = allocator.alloc(usize, word_capacity); + for (0..word_capacity) |i| { + words[i] = 0; + } + return BitVector{ .bits = words, .length = 0 }; } - // push(bv: *BitVector) → void - fn push(bv: *BitVector) -> void { - // TODO: Implement from .tri spec + // push(bv: *BitVector, bit: bool, allocator: std.mem.Allocator) → !void + fn push(bv: *BitVector, bit: bool, allocator: std.mem.Allocator) -> !void { + const word_index = bv.length / WORD_BITS; + const bit_offset = bv.length % WORD_BITS; + + if (word_index >= bv.bits.len) { + const new_capacity = bv.bits.len * 2 + 1; + const new_words = allocator.alloc(usize, new_capacity); + for (0..new_capacity) |i| { + new_words[i] = if (i < bv.bits.len) bv.bits[i] else 0; + } + allocator.free(bv.bits); + bv.bits = new_words; + } + + if (bit) { + bv.bits[word_index] |= (1 << bit_offset); + } else { + bv.bits[word_index] &= ~(1 << bit_offset); + } + + bv.length += 1; } - // pop(bv: *BitVector) → void - fn pop(bv: *BitVector) -> void { - // TODO: Implement from .tri spec + // pop(bv: *BitVector) → ?bool + fn pop(bv: *BitVector) -> ?bool { + if (bv.length == 0) { + return null; + } + + bv.length -= 1; + const word_index = bv.length / WORD_BITS; + const bit_offset = bv.length % WORD_BITS; + const bit = (bv.bits[word_index] >> bit_offset) & 1; + + return bit == 1; } - // get(bv: BitVector) → void - fn get(bv: BitVector) -> void { - // TODO: Implement from .tri spec + // get(bv: BitVector, index: usize) → bool + fn get(bv: BitVector, index: usize) -> bool { + const word_index = index / WORD_BITS; + const bit_offset = index % WORD_BITS; + const word = bv.bits[word_index]; + const bit = (word >> bit_offset) & 1; + return bit == 1; } - // set(bv: *BitVector) → void - fn set(bv: *BitVector) -> void { - // TODO: Implement from .tri spec + // set(bv: *BitVector, index: usize, value: bool) → void + fn set(bv: *BitVector, index: usize, value: bool) -> void { + const word_index = index / WORD_BITS; + const bit_offset = index % WORD_BITS; + + if (value) { + bv.bits[word_index] |= (1 << bit_offset); + } else { + bv.bits[word_index] &= ~(1 << bit_offset); + } } - // len(bv: BitVector) → void - fn len(bv: BitVector) -> void { - // TODO: Implement from .tri spec + // len(bv: BitVector) → usize + fn len(bv: BitVector) -> usize { + return bv.length; } - // append(bv: *BitVector) → void - fn append(bv: *BitVector) -> void { - // TODO: Implement from .tri spec + // append(bv: *BitVector, other: BitVector, allocator: std.mem.Allocator) → !void + fn append(bv: *BitVector, other: BitVector, allocator: std.mem.Allocator) -> !void { + for (0..other.length) |i| { + push(bv, get(other, i), allocator); + } } // ═══════════════════════════════════════════════════════════ @@ -103,3 +151,85 @@ module TriBitvector; when result = append(input) then result != undefined + test word_capacity_covers_the_bit_length + // Verify: `bits` is measured in machine words and `length` in bits, so + // the backing slice must span at least `length` bits + given words = [_]usize{ 0, 0 } + and bv = BitVector{ .bits = @constCast(&words), .length = 65 } + when capacity = bv.bits.len * @bitSizeOf(usize) + then capacity >= bv.length and bv.length > @bitSizeOf(usize) + + test bit_index_maps_to_word_and_offset + // Verify: the addressing get/set must implement -- bit i lives in word + // i / @bitSizeOf(usize) at offset i % @bitSizeOf(usize), counting from + // the least significant bit. Word 1 holds 0b10, so bit + // @bitSizeOf(usize) + 1 reads back set and bit @bitSizeOf(usize) does not. + given words = [_]usize{ 0, 2 } + and bv = BitVector{ .bits = @constCast(&words), .length = @bitSizeOf(usize) + 2 } + and idx = @bitSizeOf(usize) + 1 + when word = bv.bits[idx / @bitSizeOf(usize)] + and bit = (word >> (idx % @bitSizeOf(usize))) & 1 + and neighbour = word & 1 + then bit == 1 and neighbour == 0 + + test empty_returns_zero_length + given bv = empty() + then bv.length == 0 and bv.bits.len == 0 + + test with_capacity_calculates_correct_word_count + given bv = with_capacity(65, allocator) + then bv.length == 0 and bv.bits.len == (65 + WORD_BITS - 1) / WORD_BITS + + test push_increases_length_and_sets_bit + given bv = empty() + and allocator = allocator + when push(bv, true, allocator) + and push(bv, false, allocator) + then bv.length == 2 and get(bv, 0) == true and get(bv, 1) == false + + test pop_decreases_length_and_returns_correct_bit + given bv = empty() + and allocator = allocator + when push(bv, true, allocator) + and push(bv, false, allocator) + and bit = pop(bv) + then bv.length == 1 and bit == false and get(bv, 0) == true + + test pop_returns_null_on_empty_vector + given bv = empty() + when bit = pop(bv) + then bit == null + + test get_reads_the_bit_at_word_offset + given words = [_]usize{ 0, 2 } + and bv = BitVector{ .bits = @constCast(&words), .length = 66 } + when bit = get(bv, 65) + and neighbour = get(bv, 64) + then bit == true and neighbour == false + + test set_modifies_bit_in_place + given words = [_]usize{ 0 } + and bv = BitVector{ .bits = @constCast(&words), .length = 3 } + when set(bv, 0, true) + and set(bv, 1, false) + and set(bv, 2, true) + then get(bv, 0) == true and get(bv, 1) == false and get(bv, 2) == true + + test len_returns_correct_bit_count + given bv = empty() + and allocator = allocator + when push(bv, true, allocator) + and push(bv, false, allocator) + and push(bv, true, allocator) + then len(bv) == 3 + + test appends_all_bits_from_other_vector + given bv1 = empty() + and bv2 = empty() + and allocator = allocator + when push(bv1, true, allocator) + and push(bv1, false, allocator) + and push(bv2, false, allocator) + and push(bv2, true, allocator) + and append(bv1, bv2, allocator) + then len(bv1) == 4 and get(bv1, 0) == true and get(bv1, 1) == false and get(bv1, 2) == false and get(bv1, 3) == true \ No newline at end of file