Libove Blog

Personal Blog about anything - mostly programming, cooking and random thoughts

I've started working on a minimal TOML parser in #zig for my game. This is already enough to parse a file into a map of structs. I'm really impressed by the comptime type info.

const std = @import("std");

const ParserError = error{
    MissingEqual,
    MissingBraket,
    MissingQuote,
    UnsupportedType,
};

pub fn parse_file(
    comptime T: type,
    allocator: std.mem.Allocator,
    file_name: []const u8,
) !std.StringHashMap(T) {
    var map = std.StringHashMap(T).init(allocator);

    var file = try std.fs.cwd().openFile(file_name, .{});
    defer file.close();
    var buf_reader = std.io.bufferedReader(file.reader());
    var in_stream = buf_reader.reader();
    var buf: [1024]u8 = undefined;
    var name: ?[]const u8 = null;
    var cur_item: T = undefined;
    while (try in_stream.readUntilDelimiterOrEof(&buf, '\n')) |line| {
        const clean = std.mem.trim(u8, line, &std.ascii.whitespace);
        // skip empty
        if (clean.len == 0) {
            continue;
        }

        if (clean[0] == '[') {
            if (name != null) {
                try map.put(name.?, cur_item);
            }
            const name_split_idx = std.mem.indexOf(u8, clean, "]") orelse return ParserError.MissingBraket;
            name = try allocator.dupe(u8, clean[1..name_split_idx]);
            continue;
        }

        const split_idx = try (std.mem.indexOf(u8, clean, "=") orelse ParserError.MissingEqual);
        const field_name = std.mem.trim(u8, clean[0..split_idx], &std.ascii.whitespace);
        const value = std.mem.trim(u8, clean[split_idx + 1 ..], &std.ascii.whitespace);

        const type_info = comptime @typeInfo(T);
        const fields = comptime type_info.@"struct".fields;
        inline for (fields) |field| {
            if (std.mem.eql(u8, field_name, field.name)) {
                switch (field.type) {
                    f32, f64 => |Float| {
                        @field(cur_item, field.name) = try std.fmt.parseFloat(Float, value);
                    },
                    u8, u32, u64, i8, i32, i64 => |Int| {
                        @field(cur_item, field.name) = try std.fmt.parseInt(Int, value, 10);
                    },
                    []u8 => {
                        if (value[0] != '"' or value[value.len - 1] != '"') {
                            return ParserError.MissingQuote;
                        }
                        @field(cur_item, field.name) = try allocator.dupe(u8, value[1 .. value.len - 1]);
                    },
                    else => {
                        return ParserError.UnsupportedType;
                    },
                }
                break;
            }
        }
    }
    if (name != null) {
        try map.put(name.?, cur_item);
    }

    return map;
}