How to Parse Zon like Json during Runtime?

Based on this post:

I changed the fields of Dependency to optionals so that later code still has the info whether the field was present:

var zondata: [:0]const u8 =
    \\.{
    \\    .name = .foo,
    \\    .version = "0.0.0",
    \\    .fingerprint = 0x1879e3171df18555,
    \\    .minimum_zig_version = "0.15.1",
    \\    .dependencies = .{
    \\        .capy = .{
    \\            .url = "git+https://github.com/capy-ui/capy#bb12dab974a310b737acbaa6649cc2f2f8f8455f",
    \\            .hash = "capy-0.4.1-kru7R1I2ZQS55Kk-HN_p8YDKCdkgGhmJe8f0Gq0djDfd",
    \\        },
    \\    },
    \\    .paths = .{
    \\        "build.zig",
    \\        "build.zig.zon",
    \\        "src",
    \\    },
    \\}
;

pub fn main() !void {
    var gpa = std.heap.DebugAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

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

    const parsed = try parseBuildZigZon(arena.allocator(), zondata);

    std.debug.print("{?s}\n", .{parsed.name});

    var it = parsed.dependencies.iterator();
    while (it.next()) |dependency| {
        std.debug.print("{s}\n", .{dependency.key_ptr.*});
        std.debug.print("{?s}\n", .{dependency.value_ptr.url});
        std.debug.print("{?s}\n", .{dependency.value_ptr.hash});
        std.debug.print("{?s}\n", .{dependency.value_ptr.path});
        std.debug.print("{?}\n", .{dependency.value_ptr.lazy});
    }
}

const BuildZigZon = struct {
    name: ?[]const u8 = null,
    version: ?[]const u8 = null,
    dependencies: std.StringArrayHashMapUnmanaged(Dependency) = .empty,

    const Dependency = struct {
        url: ?[]const u8 = null,
        hash: ?[]const u8 = null,
        path: ?[]const u8 = null,
        lazy: ?bool = null,
    };
};

fn parseBuildZigZon(arena: std.mem.Allocator, content: [:0]const u8) !BuildZigZon {
    const ast = try std.zig.Ast.parse(arena, content, .zon);
    const zoir = try std.zig.ZonGen.generate(arena, ast, .{ .parse_str_lits = true });

    const root = std.zig.Zoir.Node.Index.root.get(zoir);
    const root_struct = if (root == .struct_literal) root.struct_literal else return error.Parse;

    var result: BuildZigZon = .{};

    for (root_struct.names, 0..root_struct.vals.len) |name_node, index| {
        const value = root_struct.vals.at(@intCast(index));
        const name = name_node.get(zoir);

        if (std.mem.eql(u8, name, "name")) {
            result.name = try arena.dupe(u8, value.get(zoir).enum_literal.get(zoir));
        }

        if (std.mem.eql(u8, name, "version")) {
            result.version = try arena.dupe(u8, value.get(zoir).string_literal);
        }

        if (std.mem.eql(u8, name, "dependencies")) dep: {
            switch (value.get(zoir)) {
                .struct_literal => |sl| {
                    for (sl.names, 0..sl.vals.len) |dep_name, dep_index| {
                        const node = sl.vals.at(@intCast(dep_index));
                        const dep_body = try std.zon.parse.fromZoirNode(BuildZigZon.Dependency, arena, ast, zoir, node, null, .{});

                        try result.dependencies.put(arena, try arena.dupe(u8, dep_name.get(zoir)), dep_body);
                    }
                },
                .empty_literal => {
                    break :dep;
                },
                else => return error.Parse,
            }
        }
    }

    return result;
}

const std = @import("std");

I didn’t see a compelling reason (was too lazy) to change the StringArrayHashMap to a slice, so that is left as an exercise for the reader.

3 Likes