typetui 0.2.0

A terminal-based typing test.
Documentation
const std = @import("std");

fn demonstrate_general_purpose_allocator() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const memory = try allocator.alloc(u8, 1024);
    defer allocator.free(memory);

    @memset(memory, 0);
    std.debug.print("Allocated {} bytes with GPA\n", .{memory.len});
}

fn demonstrate_page_allocator() !void {
    const allocator = std.heap.page_allocator;

    const large_block = try allocator.alloc(u8, 4096);
    defer allocator.free(large_block);

    std.debug.print("Allocated {} bytes with page allocator\n", .{large_block.len});
}

fn demonstrate_fixed_buffer_allocator() !void {
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    const data = try allocator.alloc(u8, 256);
    defer allocator.free(data);

    @memset(data, 'A');
    std.debug.print("FBA used: {} / {} bytes\n", .{ fba.end_index, buffer.len });
}

fn demonstrate_c_allocator() void {
    const allocator = std.heap.c_allocator;

    const ptr = allocator.alloc(u8, 512) catch unreachable;
    defer allocator.free(ptr);

    std.debug.print("Allocated {} bytes with C allocator\n", .{ptr.len});
}

fn demonstrate_arena_allocator() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
    defer arena.deinit();
    const allocator = arena.allocator();

    _ = try allocator.alloc(u8, 100);
    _ = try allocator.alloc(u8, 200);
    _ = try allocator.alloc(u8, 300);

    std.debug.print("Arena allocated 3 blocks, freed all at once\n", .{});
}

const PoolAllocator = struct {
    const Self = @This();

    allocator: std.mem.Allocator,
    block_size: usize,
    blocks_per_chunk: usize,
    free_list: ?*Block,
    chunks: std.ArrayList([]u8),

    const Block = struct {
        next: ?*Block,
    };

    fn init(backing_allocator: std.mem.Allocator, block_size: usize, blocks_per_chunk: usize) Self {
        return .{
            .allocator = backing_allocator,
            .block_size = @max(block_size, @sizeOf(Block)),
            .blocks_per_chunk = blocks_per_chunk,
            .free_list = null,
            .chunks = std.ArrayList([]u8).init(backing_allocator),
        };
    }

    fn deinit(self: *Self) void {
        for (self.chunks.items) |chunk| {
            self.allocator.free(chunk);
        }
        self.chunks.deinit();
    }

    fn allocBlock(self: *Self) !*anyopaque {
        if (self.free_list == null) {
            try self.grow();
        }
        const block = self.free_list.?;
        self.free_list = block.next;
        return block;
    }

    fn freeBlock(self: *Self, ptr: *anyopaque) void {
        const block: *Block = @ptrCast(@alignCast(ptr));
        block.next = self.free_list;
        self.free_list = block;
    }

    fn grow(self: *Self) !void {
        const chunk_size = self.block_size * self.blocks_per_chunk;
        const chunk = try self.allocator.alloc(u8, chunk_size);
        try self.chunks.append(chunk);

        var i: usize = 0;
        while (i < self.blocks_per_chunk) : (i += 1) {
            const offset = i * self.block_size;
            const block: *Block = @ptrCast(@alignCast(&chunk[offset]));
            block.next = self.free_list;
            self.free_list = block;
        }
    }
};

test "pool allocator" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var pool = PoolAllocator.init(gpa.allocator(), 64, 10);
    defer pool.deinit();

    const obj1 = try pool.allocBlock();
    const obj2 = try pool.allocBlock();

    std.debug.print("Pool allocated objects: {} and {}\n", .{ obj1, obj2 });

    pool.freeBlock(obj1);
    pool.freeBlock(obj2);
}

const StackAllocator = struct {
    const Self = @This();

    allocator: std.mem.Allocator,
    buffer: []u8,
    offset: usize,
    marks: std.ArrayList(usize),

    fn init(backing_allocator: std.mem.Allocator, size: usize) !Self {
        const buffer = try backing_allocator.alloc(u8, size);
        return .{
            .allocator = backing_allocator,
            .buffer = buffer,
            .offset = 0,
            .marks = std.ArrayList(usize).init(backing_allocator),
        };
    }

    fn deinit(self: *Self) void {
        self.allocator.free(self.buffer);
        self.marks.deinit();
    }

    fn pushMark(self: *Self) !void {
        try self.marks.append(self.offset);
    }

    fn popMark(self: *Self) void {
        if (self.marks.items.len > 0) {
            self.offset = self.marks.pop();
        }
    }

    fn alloc(self: *Self, size: usize, alignment: usize) ?[]u8 {
        const aligned_offset = std.mem.alignForward(usize, self.offset, alignment);
        const new_offset = aligned_offset + size;

        if (new_offset > self.buffer.len) return null;

        const result = self.buffer[aligned_offset..new_offset];
        self.offset = new_offset;
        return result;
    }

    fn reset(self: *Self) void {
        self.offset = 0;
        self.marks.clearAndFree();
    }
};

test "stack allocator" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var stack = try StackAllocator.init(gpa.allocator(), 1024);
    defer stack.deinit();

    try stack.pushMark();
    const mem1 = stack.alloc(100, 8).?;
    const mem2 = stack.alloc(200, 8).?;

    std.debug.print("Stack alloc: {} bytes and {} bytes\n", .{ mem1.len, mem2.len });

    stack.popMark();

    const mem3 = stack.alloc(150, 8).?;
    std.debug.print("After pop: {} bytes\n", .{mem3.len});
}

fn aligned_alloc_example() !void {
    const allocator = std.heap.page_allocator;

    const alignment: usize = 64;
    const size: usize = 256;

    const ptr = try allocator.allocAdvanced(u8, alignment, size, .exact);
    defer allocator.free(ptr);

    const addr = @intFromPtr(ptr.ptr);
    std.debug.print("Aligned allocation at 0x{x} (aligned to {}): {}\n", .{ addr, alignment, addr % alignment == 0 });
}

fn realloc_example() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var buf = try allocator.alloc(u8, 16);
    defer allocator.free(buf);

    @memcpy(buf[0..5], "Hello");
    std.debug.print("Original: {s}\n", .{buf[0..5]});

    buf = try allocator.realloc(buf, 32);
    @memcpy(buf[5..11], " World");
    std.debug.print("Reallocated: {s}\n", .{buf[0..11]});
}

fn allocator_vtable_example() !void {
    const MyAlloc = struct {
        counter: usize = 0,

        fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
            _ = ret_addr;
            const self: *@This() = @ptrCast(@alignCast(ctx));
            self.counter += 1;
            _ = ptr_align;
            _ = len;
            return null;
        }

        fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
            _ = ctx;
            _ = buf;
            _ = buf_align;
            _ = new_len;
            _ = ret_addr;
            return false;
        }

        fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
            _ = ctx;
            _ = buf;
            _ = buf_align;
            _ = ret_addr;
        }
    };

    var my_alloc: MyAlloc = .{};
    _ = my_alloc;
}

pub fn main() !void {
    std.debug.print("=== General Purpose Allocator ===\n", .{});
    try demonstrate_general_purpose_allocator();

    std.debug.print("\n=== Page Allocator ===\n", .{});
    try demonstrate_page_allocator();

    std.debug.print("\n=== Fixed Buffer Allocator ===\n", .{});
    demonstrate_fixed_buffer_allocator();

    std.debug.print("\n=== Arena Allocator ===\n", .{});
    try demonstrate_arena_allocator();

    std.debug.print("\n=== Realloc Example ===\n", .{});
    try realloc_example();
}