const std = @import("std"); const engine = @import("engine"); const model_types = @import("model.zig"); /// Compose the generated Zig projection from generic and product registries. pub fn render(model: *const model_types.Model, writer: *std.Io.Writer) !void { try writer.writeAll( \\// Generated from spec/model/*.zon by spec-tool. Do not edit. \\const std = @import("std"); \\ ); try renderPackage(&model.package, writer); try engine.codegen.renderConcepts(model.graph.concepts, writer); try renderAndroidPackages(model.android_packages, writer); try renderClassification(&model.classification, writer); try renderThresholds(model.thresholds, writer); try renderLimits(model.limits, writer); try renderCli(&model.cli, writer); try engine.codegen.renderVerification(model.graph.verification, writer); try engine.codegen.renderImplementationUnits(model.graph.implementations, writer); try engine.codegen.renderTestSpecs(model.graph.test_specs, writer); try writer.writeAll( \\test "generated specification registries are populated and indexed" { \\ try std.testing.expect(concepts.len > 0); \\ try std.testing.expect(limits.len > 0); \\ try std.testing.expect(android_packages.len > 0); \\ try std.testing.expect(classification_source_basenames.len > 0); \\ try std.testing.expect(classification_source_suffixes.len > 0); \\ try std.testing.expect(classification_document_roots.len > 0); \\ try std.testing.expect(thresholds.len > 0); \\ try std.testing.expect(cli_options.len > 0); \\ try std.testing.expect(cli_exit_statuses.len > 0); \\ try std.testing.expect(verification_obligations.len > 0); \\ try std.testing.expect(implementation_units.len > 0); \\ try std.testing.expect(test_specs.len > 0); \\ for (concepts, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (limits, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (android_packages, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (thresholds, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (cli_options, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (cli_exit_statuses, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (verification_obligations, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (implementation_units, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\ for (test_specs, 0..) |item, index| { \\ try std.testing.expectEqual(index, @intFromEnum(item.id)); \\ } \\} \\ ); } /// Project the one canonical version reported by the executable and the manual. pub fn renderPackage(package: *const model_types.Package, writer: *std.Io.Writer) !void { try writer.writeAll("pub const artifact_version = "); try engine.codegen.writeZigString(writer, package.version); try writer.writeAll(";\n\n"); } pub fn renderAndroidPackages( packages: []const model_types.AndroidPackage, writer: *std.Io.Writer, ) !void { try writer.writeAll( \\pub const AndroidPackageId = enum { \\ ); for (packages) |package| try writer.print(" {s},\n", .{package.id}); try writer.writeAll( \\}; \\pub const AndroidPackageRole = enum { base, add_on }; \\pub const AndroidPackage = struct { \\ id: AndroidPackageId, \\ revision: u32, \\ identifier: []const u8, \\ role: AndroidPackageRole, \\}; \\ \\pub const android_packages = [_]AndroidPackage{ \\ ); for (packages) |package| { try writer.print( " .{{ .id = .{s}, .revision = {d}, .identifier = ", .{ package.id, package.revision }, ); try engine.codegen.writeZigString(writer, package.identifier); try writer.print(", .role = .{s} }},\n", .{@tagName(package.role)}); } try writer.writeAll( \\}; \\ \\pub fn androidPackage(id: AndroidPackageId) *const AndroidPackage { \\ return &android_packages[@intFromEnum(id)]; \\} \\ ); } pub fn renderClassification( classification: *const model_types.Classification, writer: *std.Io.Writer, ) !void { try renderClassificationSet("source_basenames", &classification.source_basenames, writer); try renderClassificationSet("source_suffixes", &classification.source_suffixes, writer); try renderClassificationSet("document_roots", &classification.document_roots, writer); } fn renderClassificationSet( name: []const u8, set: *const model_types.ClassificationSet, writer: *std.Io.Writer, ) !void { try writer.print("pub const classification_{s} = [_][]const u8{{\n", .{name}); for (set.values) |value| { try writer.writeAll(" "); try engine.codegen.writeZigString(writer, value); try writer.writeAll(",\n"); } try writer.writeAll("};\n\n"); } pub fn renderThresholds( thresholds: []const model_types.Threshold, writer: *std.Io.Writer, ) !void { try writer.writeAll("pub const ThresholdId = enum {\n"); for (thresholds) |threshold| try writer.print(" {s},\n", .{threshold.id}); try writer.writeAll( "};\npub const ThresholdUnit = enum { bytes, seconds };\n" ++ "pub const Threshold = struct { id: ThresholdId, key: []const u8, " ++ "value: u64, unit: ThresholdUnit };\n\npub const thresholds = [_]Threshold{\n", ); for (thresholds) |threshold| { try writer.print( " .{{ .id = .{s}, .key = ", .{threshold.id}, ); try engine.codegen.writeZigString(writer, threshold.key); try writer.print(", .value = {d}, .unit = .{s} }},\n", .{ threshold.value, @tagName(threshold.unit), }); } try writer.writeAll("};\n\npub const threshold_value = struct {\n"); for (thresholds) |threshold| { try writer.print(" pub const {s}: u64 = {d};\n", .{ threshold.id, threshold.value }); } try writer.writeAll("};\n\n"); } pub fn renderLimits(limits: []const model_types.Limit, writer: *std.Io.Writer) !void { try writer.writeAll( \\pub const LimitId = enum { \\ ); for (limits) |limit| try writer.print(" {s},\n", .{limit.id}); try writer.writeAll( \\}; \\ \\pub const LimitCategory = enum { scan, process, queue, terminal, logging }; \\pub const LimitUnit = enum { \\ bytes, \\ cells, \\ components, \\ count, \\ milliseconds, \\ minutes, \\ seconds, \\}; \\pub const ScalarLimit = struct { value: u64, unit: LimitUnit }; \\pub const DimensionsLimit = struct { columns: u16, rows: u16 }; \\pub const AlternativeLimit = struct { items: u32, milliseconds: u32 }; \\pub const LimitValue = union(enum) { \\ scalar: ScalarLimit, \\ dimensions: DimensionsLimit, \\ alternative: AlternativeLimit, \\}; \\pub const Limit = struct { \\ id: LimitId, \\ category: LimitCategory, \\ label: []const u8, \\ value: LimitValue, \\}; \\ \\pub const limits = [_]Limit{ \\ ); for (limits) |limit| try renderLimit(limit, writer); try writer.writeAll( \\}; \\ \\pub fn limit(id: LimitId) *const Limit { \\ return &limits[@intFromEnum(id)]; \\} \\ \\pub const limit_value = struct { \\ ); for (limits) |limit| try renderLimitConstant(limit, writer); try writer.writeAll("};\n\n"); } pub fn renderLimit(limit: model_types.Limit, writer: *std.Io.Writer) !void { try writer.print( " .{{\n .id = .{s},\n .category = .{s},\n .label = ", .{ limit.id, @tagName(limit.category) }, ); try engine.codegen.writeZigString(writer, limit.label); try writer.writeAll(",\n .value = "); switch (limit.value) { .scalar => |scalar| try writer.print( ".{{ .scalar = .{{ .value = {d}, .unit = .{s} }} }}", .{ scalar.value, @tagName(scalar.unit) }, ), .dimensions => |dimensions| try writer.print( ".{{ .dimensions = .{{ .columns = {d}, .rows = {d} }} }}", .{ dimensions.columns, dimensions.rows }, ), .alternative => |alternative| try writer.print( ".{{ .alternative = .{{ .items = {d}, .milliseconds = {d} }} }}", .{ alternative.items, alternative.milliseconds }, ), } try writer.writeAll(",\n },\n"); } pub fn renderLimitConstant(limit: model_types.Limit, writer: *std.Io.Writer) !void { switch (limit.value) { .scalar => |scalar| try writer.print( " pub const {s}: u64 = {d};\n", .{ limit.id, scalar.value }, ), .dimensions => |dimensions| try writer.print( " pub const {s}_columns: u16 = {d};\n" ++ " pub const {s}_rows: u16 = {d};\n", .{ limit.id, dimensions.columns, limit.id, dimensions.rows }, ), .alternative => |alternative| try writer.print( " pub const {s}_items: u32 = {d};\n" ++ " pub const {s}_milliseconds: u32 = {d};\n", .{ limit.id, alternative.items, limit.id, alternative.milliseconds }, ), } } pub fn renderCli(cli: *const model_types.Cli, writer: *std.Io.Writer) !void { try writer.writeAll("pub const cli_executable = "); try engine.codegen.writeZigString(writer, cli.executable); try writer.writeAll(";\npub const cli_description =\n "); try engine.codegen.writeZigString(writer, cli.description); try writer.writeAll(";\npub const cli_default_mode = "); try engine.codegen.writeZigString(writer, cli.default_mode); try writer.print( ";\npub const cli_long_options_only = {};\n" ++ "pub const cli_option_terminator = {};\n" ++ "pub const cli_operands_allowed = {};\n" ++ "pub const cli_duplicate_options_allowed = {};\n" ++ "pub const cli_unattended_cleanup = {};\n" ++ "pub const OutputStream = enum {{ stdout, stderr }};\n" ++ "pub const cli_informational_stream: OutputStream = .{s};\n" ++ "pub const cli_usage_stream: OutputStream = .{s};\n" ++ "pub const cli_diagnostic_stream: OutputStream = .{s};\n\n", .{ cli.long_options_only, cli.option_terminator, cli.operands_allowed, cli.duplicate_options_allowed, cli.unattended_cleanup, @tagName(cli.informational_stream), @tagName(cli.usage_stream), @tagName(cli.diagnostic_stream), }, ); try renderCliOptions(cli.options, writer); try renderCliArgumentRules(cli.argument_rules, writer); try renderCliExitStatuses(cli.exit_statuses, writer); try renderCliHelp(cli, writer); } pub fn renderCliOptions(options: []const model_types.CliOption, writer: *std.Io.Writer) !void { try writer.writeAll( \\pub const CliOptionId = enum { \\ ); for (options) |option| try writer.print(" {s},\n", .{option.id}); try writer.writeAll( \\}; \\pub const CliArgument = enum { none, path }; \\pub const StartupSubsystem = enum { \\ configuration, \\ logging, \\ terminal_initialization, \\ scanning, \\}; \\pub const CliOption = struct { \\ id: CliOptionId, \\ spelling: []const u8, \\ argument: CliArgument, \\ summary: []const u8, \\ must_appear_alone: bool, \\ bypasses_startup: []const StartupSubsystem, \\ requires_terminal: bool, \\}; \\pub const cli_options = [_]CliOption{ \\ ); for (options) |option| try renderCliOption(option, writer); try writer.writeAll( \\}; \\pub fn cliOption(id: CliOptionId) *const CliOption { \\ return &cli_options[@intFromEnum(id)]; \\} \\ ); } pub fn renderCliOption(option: model_types.CliOption, writer: *std.Io.Writer) !void { try writer.print(" .{{\n .id = .{s},\n .spelling = ", .{option.id}); try engine.codegen.writeZigString(writer, option.spelling); try writer.print( ",\n .argument = .{s},\n .summary = ", .{@tagName(option.argument)}, ); try engine.codegen.writeZigString(writer, option.summary); try writer.print(",\n .must_appear_alone = {},\n", .{option.must_appear_alone}); if (option.bypasses_startup.len == 0) { try writer.writeAll(" .bypasses_startup = &.{},\n"); } else { try writer.writeAll(" .bypasses_startup = &.{\n"); for (option.bypasses_startup) |subsystem| { try writer.print(" .{s},\n", .{@tagName(subsystem)}); } try writer.writeAll(" },\n"); } try writer.writeAll(" .requires_terminal = "); try writer.print("{},\n }},\n", .{option.requires_terminal}); } pub fn renderCliArgumentRules( rules: []const model_types.CliArgumentRule, writer: *std.Io.Writer, ) !void { try writer.writeAll( \\pub const ArgumentSyntax = enum { separate }; \\pub const RelativeBase = enum { initial_working_directory }; \\pub const CliArgumentRule = struct { \\ option: CliOptionId, \\ argument: []const u8, \\ arity: u8, \\ syntax: ArgumentSyntax, \\ nonempty: bool, \\ relative_base: RelativeBase, \\}; \\pub const cli_argument_rules = [_]CliArgumentRule{ \\ ); for (rules) |rule| { try writer.print(" .{{\n .option = .{s},\n .argument = ", .{rule.option}); try engine.codegen.writeZigString(writer, rule.argument); try writer.print( ",\n .arity = {d},\n .syntax = .{s},\n" ++ " .nonempty = {},\n .relative_base = .{s},\n }},\n", .{ rule.arity, @tagName(rule.syntax), rule.nonempty, @tagName(rule.relative_base), }, ); } try writer.writeAll("};\n\n"); } pub fn renderCliExitStatuses( statuses: []const model_types.CliExitStatus, writer: *std.Io.Writer, ) !void { try writer.writeAll( \\pub const CliExitStatusId = enum { \\ ); for (statuses) |status| try writer.print(" {s},\n", .{status.id}); try writer.writeAll( \\}; \\pub const CliExitStatus = struct { \\ id: CliExitStatusId, \\ code: u8, \\ condition: []const u8, \\}; \\pub const cli_exit_statuses = [_]CliExitStatus{ \\ ); for (statuses) |status| { try writer.print( " .{{\n .id = .{s},\n .code = {d},\n .condition = ", .{ status.id, status.code }, ); try engine.codegen.writeZigStringLines(writer, status.condition, " ", 75); try writer.writeAll(" },\n"); } try writer.writeAll( \\}; \\pub fn cliExitCode(id: CliExitStatusId) u8 { \\ return cli_exit_statuses[@intFromEnum(id)].code; \\} \\ ); } pub fn renderCliHelp(cli: *const model_types.Cli, writer: *std.Io.Writer) !void { const display_width = cliOptionDisplayWidth(cli); try writer.writeAll( \\pub const cli_help = \\ "Usage: " ++ cli_executable ++ " [OPTIONS]\n\n" ++ \\ cli_description ++ "\n\n" ++ \\ "Options:\n" ++ \\ ); for (cli.options) |option| { try writer.writeAll(" \" \" ++ "); try engine.codegen.writeZigString(writer, option.spelling); const argument = findCliArgumentRule(cli.argument_rules, option.id); if (argument) |rule| { try writer.writeAll(" ++ \" \" ++ "); try engine.codegen.writeZigString(writer, rule.argument); } const option_width = cliOptionWidth(&option, argument); try writer.writeAll(" ++ "); try writeRepeatedSpaces(writer, display_width - option_width + 2); try writer.writeAll(" ++\n "); try engine.codegen.writeZigString(writer, option.summary); if (option.must_appear_alone) { try writer.writeAll(" ++\n \" Must appear alone.\""); } try writer.writeAll(" ++ \"\\n\" ++\n"); } if (!cli.duplicate_options_allowed) { try writer.writeAll(" \"\\nEach option may appear at most once.\\n\" ++\n"); } if (!cli.operands_allowed) { try writer.writeAll(" \"Operands are not supported.\\n\" ++\n"); } if (!cli.unattended_cleanup) { try writer.writeAll(" \"Unattended cleanup is not supported.\\n\";\n\n"); } else { try writer.writeAll(" \"\";\n\n"); } } pub fn cliOptionDisplayWidth(cli: *const model_types.Cli) usize { var width: usize = 0; for (cli.options) |option| { const argument = findCliArgumentRule(cli.argument_rules, option.id); width = @max(width, cliOptionWidth(&option, argument)); } return width; } pub fn cliOptionWidth( option: *const model_types.CliOption, argument: ?model_types.CliArgumentRule, ) usize { if (argument) |rule| return option.spelling.len + 1 + rule.argument.len; return option.spelling.len; } pub fn findCliArgumentRule( rules: []const model_types.CliArgumentRule, option_id: []const u8, ) ?model_types.CliArgumentRule { for (rules) |rule| if (std.mem.eql(u8, rule.option, option_id)) return rule; return null; } pub fn writeRepeatedSpaces(writer: *std.Io.Writer, count: usize) !void { try writer.writeByte('"'); for (0..count) |_| try writer.writeByte(' '); try writer.writeByte('"'); }