Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 152 additions & 22 deletions specs/tri/collections/bitvector.t27
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

// ═══════════════════════════════════════════════════════════
Expand Down Expand Up @@ -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
Loading