Luigit
repositories / termux-janitor

termux-janitor

Interactive cleanup assistant for Termux: transparent, safe, confirmed disk reclamation.

owned by admin

tools/spec-engine/src/codegen.zig

Raw
const std = @import("std");
const model_types = @import("model.zig");
const profile = @import("profile");

pub fn renderConcepts(concepts: []const model_types.Concept, writer: *std.Io.Writer) !void {
    try writer.writeAll(
        \\pub const ConceptId = enum {
        \\
    );
    for (concepts) |concept| {
        try writer.writeAll("    ");
        try writeConceptSymbol(writer, concept.id);
        try writer.writeAll(",\n");
    }
    try writer.writeAll(
        \\};
        \\pub const Concept = struct { id: ConceptId, canonical_term: []const u8 };
        \\pub const concepts = [_]Concept{
        \\
    );
    for (concepts) |concept| {
        try writer.writeAll("    .{ .id = .");
        try writeConceptSymbol(writer, concept.id);
        try writer.writeAll(", .canonical_term = ");
        try writeZigString(writer, concept.term);
        try writer.writeAll(" },\n");
    }
    try writer.writeAll(
        \\};
        \\pub fn concept(id: ConceptId) *const Concept {
        \\    return &concepts[@intFromEnum(id)];
        \\}
        \\
    );
}

pub fn writeConceptSymbol(writer: *std.Io.Writer, id: []const u8) !void {
    std.debug.assert(std.mem.startsWith(u8, id, "concept-"));
    for (id[8..]) |byte| try writer.writeByte(if (byte == '-') '_' else byte);
}

pub fn renderVerification(
    obligations: []const model_types.VerificationObligation,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll(
        \\pub const VerificationObligationId = enum {
        \\
    );
    for (obligations) |obligation| {
        try writer.writeAll("    ");
        try writePrefixedSymbol(writer, obligation.id, "obligation-");
        try writer.writeAll(",\n");
    }
    try writer.writeAll(
        \\};
        \\pub const VerificationKind = enum { required, forbidden };
        \\pub const ObligationReference = struct {
        \\    id: VerificationObligationId,
        \\    revision: u32,
        \\};
        \\pub const VerificationObligation = struct {
        \\    id: VerificationObligationId,
        \\    revision: u32,
        \\    requirement_id: []const u8,
        \\    requirement_revision: u32,
        \\    kind: VerificationKind,
        \\    label: []const u8,
        \\};
        \\pub const verification_obligations = [_]VerificationObligation{
        \\
    );
    for (obligations) |obligation| {
        try writer.writeAll("    .{ .id = .");
        try writePrefixedSymbol(writer, obligation.id, "obligation-");
        try writer.print(", .revision = {d}, .requirement_id = ", .{obligation.revision});
        try writeZigString(writer, obligation.requirement.id);
        try writer.print(", .requirement_revision = {d}, .kind = .{s}, .label = ", .{
            obligation.requirement.revision,
            @tagName(obligation.kind),
        });
        try writeZigString(writer, obligation.label);
        try writer.writeAll(" },\n");
    }
    try writer.writeAll("};\n\n");
}

pub fn renderImplementationUnits(
    units: []const model_types.ImplementationUnit,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll(
        \\pub const ImplementationUnitId = enum {
        \\
    );
    for (units) |unit| {
        try writer.writeAll("    ");
        try writePrefixedSymbol(writer, unit.id, "implementation-");
        try writer.writeAll(",\n");
    }
    try writer.writeAll(
        \\};
        \\pub const ImplementationUnit = struct {
        \\    id: ImplementationUnitId,
        \\    revision: u32,
        \\    sources: []const []const u8,
        \\    realizes: []const ObligationReference,
        \\};
        \\pub const implementation_units = [_]ImplementationUnit{
        \\
    );
    for (units) |unit| {
        try writer.writeAll("    .{\n        .id = .");
        try writePrefixedSymbol(writer, unit.id, "implementation-");
        try writer.print(",\n        .revision = {d},\n        .sources = &.{{\n", .{
            unit.revision,
        });
        for (unit.sources) |source| {
            try writer.writeAll("            ");
            try writeZigString(writer, source.path);
            try writer.writeAll(",\n");
        }
        try writer.writeAll("        },\n        .realizes = &.{\n");
        for (unit.realizes) |reference| try renderObligationReference(reference, 12, writer);
        try writer.writeAll("        },\n    },\n");
    }
    try writer.writeAll("};\n\n");
}

pub fn renderTestSpecs(
    test_specs: []const model_types.TestSpec,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll(
        \\pub const TestSpecId = enum {
        \\
    );
    for (test_specs) |test_spec| {
        try writer.writeAll("    ");
        try writeTestSpecSymbol(writer, test_spec.id);
        try writer.writeAll(",\n");
    }
    try writer.writeAll(
        \\};
        \\pub const TestSuite = enum {
    );
    try writer.writeByte('\n');
    inline for (comptime std.meta.tags(profile.Suite)) |suite| {
        if (comptime profile.suiteIsConcrete(suite)) {
            try writer.print("    {s},\n", .{@tagName(suite)});
        }
    }
    try writer.writeAll(
        \\};
        \\pub const ProductionBoundary = enum { process, state_transition };
        \\pub const FixtureReference = struct {
        \\    id: []const u8,
        \\    revision: u32,
        \\    path: []const u8,
        \\};
        \\pub const VerificationClaim = struct {
        \\    oracle: []const u8,
        \\    kind: VerificationKind,
        \\    obligations: []const ObligationReference,
        \\};
        \\pub const TestSpec = struct {
        \\    id: TestSpecId,
        \\    revision: u32,
        \\    suite: TestSuite,
        \\    fixture: FixtureReference,
        \\    claims: []const VerificationClaim,
        \\    production_boundary: ProductionBoundary,
        \\};
        \\pub const test_specs = [_]TestSpec{
        \\
    );
    for (test_specs) |test_spec| try renderTestSpec(&test_spec, writer);
    try writer.writeAll("};\n\n");
}

pub fn renderTestSpec(test_spec: *const model_types.TestSpec, writer: *std.Io.Writer) !void {
    try writer.writeAll("    .{\n        .id = .");
    try writeTestSpecSymbol(writer, test_spec.id);
    try writer.print(",\n        .revision = {d},\n        .suite = .{s},\n", .{
        test_spec.revision,
        @tagName(test_spec.suite),
    });
    try writer.writeAll("        .fixture = .{\n            .id = ");
    try writeZigString(writer, test_spec.fixture.id);
    try writer.print(",\n            .revision = {d},\n            .path = ", .{
        test_spec.fixture.revision,
    });
    try writeZigString(writer, test_spec.fixture.path);
    try writer.writeAll(",\n        },\n        .claims = &.{\n");
    for (test_spec.claims) |claim| {
        try writer.writeAll("            .{ .oracle = ");
        try writeZigString(writer, claim.oracle);
        try writer.print(", .kind = .{s}, .obligations = &.{{\n", .{@tagName(claim.kind)});
        for (claim.obligations) |reference| try renderObligationReference(reference, 16, writer);
        try writer.writeAll("            } },\n");
    }
    try writer.print("        }},\n        .production_boundary = .{s},\n    }},\n", .{
        @tagName(test_spec.production_boundary),
    });
}

pub fn renderObligationReference(
    reference: model_types.ObligationReference,
    indent: usize,
    writer: *std.Io.Writer,
) !void {
    try writeIndent(writer, indent);
    try writer.writeAll(".{ .id = .");
    try writePrefixedSymbol(writer, reference.id, "obligation-");
    try writer.print(", .revision = {d} }},\n", .{reference.revision});
}

pub fn writeIndent(writer: *std.Io.Writer, indent: usize) !void {
    for (0..indent) |_| try writer.writeByte(' ');
}

pub fn writePrefixedSymbol(writer: *std.Io.Writer, id: []const u8, prefix: []const u8) !void {
    std.debug.assert(std.mem.startsWith(u8, id, prefix));
    for (id[prefix.len..]) |byte| try writer.writeByte(if (byte == '-') '_' else byte);
}

pub fn writeTestSpecSymbol(writer: *std.Io.Writer, id: []const u8) !void {
    std.debug.assert(std.mem.startsWith(u8, id, "test-"));
    for (id[5..]) |byte| try writer.writeByte(if (byte == '-') '_' else byte);
}

pub fn writeZigStringLines(
    writer: *std.Io.Writer,
    source: []const u8,
    indent: []const u8,
    width_max: usize,
) !void {
    var offset: usize = 0;
    while (offset < source.len) {
        const segment_width_max = if (offset == 0) width_max else 84;
        var end = @min(offset + segment_width_max, source.len);
        if (end < source.len) {
            const relative = std.mem.lastIndexOfScalar(u8, source[offset..end], ' ') orelse
                return error.UnbreakableGeneratedString;
            end = offset + relative + 1;
        }
        if (offset > 0) try writer.writeAll(indent);
        try writeZigString(writer, source[offset..end]);
        if (end < source.len) {
            try writer.writeAll(" ++\n");
        } else {
            try writer.writeAll(",\n");
        }
        offset = end;
    }
}

pub fn writeZigString(writer: *std.Io.Writer, source: []const u8) !void {
    const hex = "0123456789abcdef";
    try writer.writeByte('"');
    for (source) |byte| switch (byte) {
        '"' => try writer.writeAll("\\\""),
        '\\' => try writer.writeAll("\\\\"),
        '\n' => try writer.writeAll("\\n"),
        '\r' => try writer.writeAll("\\r"),
        '\t' => try writer.writeAll("\\t"),
        0...8, 11...12, 14...0x1f, 0x7f => {
            const escaped = [_]u8{ '\\', 'x', hex[byte >> 4], hex[byte & 15] };
            try writer.writeAll(&escaped);
        },
        else => try writer.writeByte(byte),
    };
    try writer.writeByte('"');
}

test "generated test suite follows the profile" {
    var output = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer output.deinit();
    try renderTestSpecs(&.{}, &output.writer);
    for (profile.concrete_suites) |suite| {
        try std.testing.expect(std.mem.indexOf(u8, output.written(), @tagName(suite)) != null);
    }
    try std.testing.expect(
        std.mem.indexOf(u8, output.written(), @tagName(profile.wildcard_suite)) == null,
    );
}

test "Zig string rendering escapes code-significant bytes" {
    var output = std.Io.Writer.Allocating.init(std.testing.allocator);
    defer output.deinit();
    try writeZigString(&output.writer, "a\"b\\c\n");
    try std.testing.expectEqualStrings("\"a\\\"b\\\\c\\n\"", output.written());
}