// Faux implements a pretty terrible subset of YAML // because apparently no YAML parser for Zig is good yet // and I am honestly already growing quite angered about it. // // It's insanely simple, it's meant to be simple, and it // will stay that way. // // I like to see it as it's own format, atp. // // Also, important note, the day that a proper YAML parser // comes out for Zig, this library must be pulled off // immediately and destroyed. yes. // const std = @import("std"); fn parseSimpleKV( allocator: std.mem.Allocator, input: []const u8, ) !Map { var map = Map.init(allocator); var lines = std.mem.tokenizeScalar(u8, input, '\n'); while (lines.next()) |line| { const hash_index = std.mem.indexOfScalar(u8, line, '#') orelse line.len; const trimmed = std.mem.trim(u8, line[0..hash_index], " \t\r"); if (trimmed.len == 0) continue; const colon_index = std.mem.indexOfScalar(u8, trimmed, ':') orelse continue; const key = std.mem.trim(u8, trimmed[0..colon_index], " \t"); var value = std.mem.trim(u8, trimmed[colon_index + 1 ..], " \t"); if (value.len >= 2 and ((value[0] == '"' and value[value.len - 1] == '"') or (value[0] == '\'' and value[value.len - 1] == '\''))) { const inner = value[1 .. value.len - 1]; value = try unescapeString(allocator, inner, value[0]); } else { value = try unescapeString(allocator, value, 0); } try map.put(key, value); } return map; } fn unescapeString( allocator: std.mem.Allocator, s: []const u8, quote: u8, ) ![]const u8 { var buffer = std.ArrayList(u8).init(allocator); var i: usize = 0; while (i < s.len) : (i += 1) { if (s[i] == '\\' and i + 1 < s.len) { i += 1; switch (s[i]) { 'n' => try buffer.append('\n'), 'r' => try buffer.append('\r'), 't' => try buffer.append('\t'), '\\' => try buffer.append('\\'), '"' => if (quote == '"' or quote == 0) try buffer.append('"') else { try buffer.append('\\'); try buffer.append('"'); }, '\'' => if (quote == '\'' or quote == 0) try buffer.append('\'') else { try buffer.append('\\'); try buffer.append('\''); }, else => { try buffer.append('\\'); try buffer.append(s[i]); }, } } else { try buffer.append(s[i]); } } return buffer.toOwnedSlice(); } fn populateStructFromMap(comptime T: type, dest: *T, map: Map, allocator: std.mem.Allocator) !void { const fields = std.meta.fields(T); inline for (fields) |field| { const key = field.name; const field_type = field.type; @field(dest, key) = if (map.get(key)) |raw| try parseValue(field_type, raw, allocator) else field.defaultValue() orelse return error.MissingField; } } fn parseValue(comptime T: type, raw: []const u8, allocator: std.mem.Allocator) !T { const info = @typeInfo(T); return switch (info) { .bool => std.mem.eql(u8, raw, "true"), .pointer => if (T == []const u8 or T == []u8) try allocator.dupe(u8, raw) else error.PointersUnsupported, .@"enum" => std.meta.stringToEnum(T, raw) orelse error.InvalidEnumValue, .optional => blk: { if (std.mem.eql(u8, raw, "null")) break :blk null; const child = info.optional.child; const parsed = try parseValue(child, raw, allocator); break :blk parsed; }, else => error.NotImplemented, }; } const Map = std.StringHashMap([]const u8); map: Map, arena: std.heap.ArenaAllocator, const Faux = @This(); pub fn parse(source: []const u8, allocator: std.mem.Allocator) !Faux { var f = Faux{ .arena = std.heap.ArenaAllocator.init(allocator), .map = undefined, }; f.map = try parseSimpleKV(f.arena.allocator(), source); return f; } pub fn toStructure(self: *Faux, comptime T: type) !T { var t: T = undefined; try populateStructFromMap(T, &t, self.map, self.arena.allocator()); return t; } pub fn deinit(self: *Faux) void { self.arena.deinit(); }