~ntgg/zosh

5742e9537221b543fb43ebe9a29a5ae13d810e6b — Noah Graff 4 years ago e0a015a
added TextBuffer type
1 files changed, 137 insertions(+), 0 deletions(-)

A src/text_buffer.zig
A src/text_buffer.zig => src/text_buffer.zig +137 -0
@@ 0,0 1,137 @@
const std = @import("std");
const Allocator = std.mem.Allocator;

pub const TextBuffer = struct {
    bytes: []u8,
    pos: Pos,
    allocator: *Allocator,

    pub const Range = struct {
        start: Pos,
        end: Pos,
    };

    pub const Pos = struct {
        /// The offset into text. 0 indexed.
        offset: usize,
        /// Line of text. 1 indexed.
        line: usize,
        /// Column of text. 1 indexed.
        column: usize,
    };

    pub fn init(allocator: *Allocator, text: []const u8) !TextBuffer {
        return TextBuffer{
            .bytes = try std.mem.dupe(allocator, u8, text),
            .pos = Pos{
                .offset = 0,
                .line = 1,
                .column = 1,
            },
            .allocator = allocator,
        };
    }

    pub fn deinit(buf: TextBuffer) void {
        buf.allocator.free(buf.bytes);
    }

    pub fn hasRemaining(buf: TextBuffer, length: usize) bool {
        return buf.bytes.len >= buf.pos.offset + length;
    }

    pub fn at(buf: TextBuffer, idx: usize) u8 {
        return buf.bytes[buf.pos.offset + idx];
    }

    pub fn append(buf: *TextBuffer, text: []const u8) !void {
        const old_end = buf.bytes.len;
        buf.bytes = try buf.allocator.realloc(buf.bytes, buf.bytes.len + text.len);
        std.mem.copy(u8, buf.bytes[old_end..], text);
    }

    /// Returns a slice into the bytes of the TextBuffer with
    /// a length <= amount.
    pub fn peek(buf: TextBuffer, amount: usize) []const u8 {
        const to_read = std.math.min(buf.bytes.len - buf.pos.offset, amount);
        return buf.bytes[buf.pos.offset .. buf.pos.offset + to_read];
    }

    /// Observe the next character.
    pub fn peekChar(buf: TextBuffer) ?u8 {
        if (buf.pos.offset < buf.bytes.len) {
            return buf.bytes[buf.pos.offset];
        }
        return null;
    }

    /// Returns a slice into the bytes of the TextBuffer with
    /// a length <= amount. Consumes the text read.
    pub fn read(buf: *TextBuffer, amount: usize) []const u8 {
        const peeked = buf.peek(amount);
        for (peeked) |c| {
            if (c == '\n') {
                buf.pos.line += 1;
                buf.pos.column = 1;
            } else {
                buf.pos.column += 1;
            }
            buf.pos.offset += 1;
        }
        return peeked;
    }

    /// Observe the next character. Consumes the character.
    pub fn readChar(buf: *TextBuffer) ?u8 {
        const c = buf.read(1);
        if (c.len < 1) return null;
        return c[0];
    }

    /// Consume all text up to and including the next new line character.
    pub fn consumeLine(buf: *TextBuffer) void {
        while (buf.readChar()) |c| {
            if (c == '\n') {
                return;
            }
        }
    }
};

test "TextBuffer tests" {
    const t = std.testing;
    const null_u8: ?u8 = null;
    var buffer = try TextBuffer.init(
        std.heap.direct_allocator,
        \\echo hi | bye
        \\line 2
        \\ line 3	with tab
        \\eof?
    );
    defer buffer.deinit();
    t.expectEqual(TextBuffer.Pos{ .offset = 0, .line = 1, .column = 1 }, buffer.pos);
    t.expectEqual(u8('e'), buffer.at(0));
    t.expectEqual(u8('e'), buffer.peekChar().?);
    t.expectEqual(u8('e'), buffer.peekChar().?);
    t.expectEqual(TextBuffer.Pos{ .offset = 0, .line = 1, .column = 1 }, buffer.pos);
    t.expectEqual(u8('e'), buffer.readChar().?);
    t.expectEqual(TextBuffer.Pos{ .offset = 1, .line = 1, .column = 2 }, buffer.pos);
    t.expectEqual(u8('c'), buffer.peekChar().?);
    t.expectEqual(u8('c'), buffer.readChar().?);
    t.expectEqualSlices(u8, "ho hi | ", buffer.peek(8));
    t.expectEqualSlices(u8, "ho hi | ", buffer.read(8));
    t.expectEqual(u8('b'), buffer.peekChar().?);

    buffer.consumeLine();
    t.expectEqual(u8('l'), buffer.peekChar().?);
    buffer.consumeLine();
    t.expectEqual(u8(' '), buffer.peekChar().?);
    t.expectEqualSlices(u8, " line", buffer.read(5));
    t.expectEqual(TextBuffer.Pos{ .offset = 26, .line = 3, .column = 6 }, buffer.pos);
    t.expectEqualSlices(u8, " 3\twith tab\neof?", buffer.read(18));
    t.expectEqual(null_u8, buffer.peekChar());
    try buffer.append("more!");
    t.expectEqual(u8('m'), buffer.peekChar().?);
    t.expect(buffer.hasRemaining(5));
    t.expect(!buffer.hasRemaining(6));
}