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;
}