diff --git a/src/reduce.zig b/src/reduce.zig index f238125528..cd5d69b0c6 100644 --- a/src/reduce.zig +++ b/src/reduce.zig @@ -1,13 +1,34 @@ const std = @import("std"); +const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const fatal = @import("./main.zig").fatal; const usage = - \\zig reduce [source_file] [interestingness] + \\zig reduce [options] ./checker root_source_file.zig [-- [argv]] + \\ + \\root_source_file.zig is relative to --main-mod-path. + \\ + \\checker: + \\ An executable that communicates interestingness by returning these exit codes: + \\ exit(0): interesting + \\ exit(1): unknown (infinite loop or other mishap) + \\ exit(other): not interesting + \\ + \\options: + \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name + \\ deps: [dep],[dep],... + \\ dep: [[import=]name] + \\ --deps [dep],[dep],... Set dependency names for the root package + \\ dep: [[import=]name] + \\ --main-mod-path Set the directory of the root module + \\ + \\argv: + \\ Forwarded directly to the interestingness script. \\ ; -const Interestingness = enum { interesting, boring, unknown }; +const Interestingness = enum { interesting, unknown, boring }; // Roadmap: // - add thread pool @@ -22,13 +43,44 @@ const Interestingness = enum { interesting, boring, unknown }; // - integrate the build system? pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { - const file_path = args[2]; - const interestingness_argv_template = args[3..]; + var opt_checker_path: ?[]const u8 = null; + var opt_root_source_file_path: ?[]const u8 = null; + var argv: []const []const u8 = &.{}; + + { + var i: usize = 2; // skip over "zig" and "reduce" + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (mem.startsWith(u8, arg, "-")) { + if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { + const stdout = std.io.getStdOut().writer(); + try stdout.writeAll(usage); + return std.process.cleanExit(); + } else if (mem.eql(u8, arg, "--")) { + argv = args[i + 1 ..]; + break; + } else { + fatal("unrecognized parameter: '{s}'", .{arg}); + } + } else if (opt_checker_path == null) { + opt_checker_path = arg; + } else if (opt_root_source_file_path == null) { + opt_root_source_file_path = arg; + } else { + fatal("unexpected extra parameter: '{s}'", .{arg}); + } + } + } + + const checker_path = opt_checker_path orelse + fatal("missing interestingness checker argument; see -h for usage", .{}); + const root_source_file_path = opt_root_source_file_path orelse + fatal("missing root source file path argument; see -h for usage", .{}); var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{}; - try interestingness_argv.ensureUnusedCapacity(arena, interestingness_argv_template.len + 1); - interestingness_argv.appendSliceAssumeCapacity(interestingness_argv_template); - interestingness_argv.appendAssumeCapacity(file_path); + try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1); + interestingness_argv.appendAssumeCapacity(checker_path); + interestingness_argv.appendSliceAssumeCapacity(argv); var rendered = std.ArrayList(u8).init(gpa); defer rendered.deinit(); @@ -38,7 +90,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { const source_code = try std.fs.cwd().readFileAllocOptions( arena, - file_path, + root_source_file_path, std.math.maxInt(u32), null, 1, @@ -55,38 +107,34 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { var next_gut_fn_index: u32 = 0; var fixups: std.zig.Ast.Fixups = .{}; + { + // smoke test the interestingness check + switch (try runCheck(arena, interestingness_argv.items)) { + .interesting => {}, + .boring, .unknown => |t| { + fatal("interestingness check returned {s} for unmodified input\n", .{ + @tagName(t), + }); + }, + } + } + while (true) { try fixups.gut_functions.put(arena, next_gut_fn_index, {}); rendered.clearRetainingCapacity(); try tree.renderToArrayList(&rendered, fixups); - if (std.mem.eql(u8, rendered.items, prev_rendered.items)) { + if (mem.eql(u8, rendered.items, prev_rendered.items)) { std.debug.print("no remaining transformations\n", .{}); break; } prev_rendered.clearRetainingCapacity(); try prev_rendered.appendSlice(rendered.items); - try std.fs.cwd().writeFile(file_path, rendered.items); + try std.fs.cwd().writeFile(root_source_file_path, rendered.items); - const result = try std.process.Child.run(.{ - .allocator = arena, - .argv = interestingness_argv.items, - }); - if (result.stderr.len != 0) - std.debug.print("{s}", .{result.stderr}); - const interestingness: Interestingness = switch (result.term) { - .Exited => |code| switch (code) { - 0 => .interesting, - 1 => .unknown, - else => .boring, - }, - else => b: { - std.debug.print("interestingness check aborted unexpectedly\n", .{}); - break :b .boring; - }, - }; + const interestingness = try runCheck(arena, interestingness_argv.items); std.debug.print("{s}\n", .{@tagName(interestingness)}); switch (interestingness) { .interesting => { @@ -104,3 +152,27 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } return std.process.cleanExit(); } + +fn termToInteresting(term: std.process.Child.Term) Interestingness { + return switch (term) { + .Exited => |code| switch (code) { + 0 => .interesting, + 1 => .unknown, + else => .boring, + }, + else => b: { + std.debug.print("interestingness check aborted unexpectedly\n", .{}); + break :b .boring; + }, + }; +} + +fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness { + const result = try std.process.Child.run(.{ + .allocator = arena, + .argv = argv, + }); + if (result.stderr.len != 0) + std.debug.print("{s}", .{result.stderr}); + return termToInteresting(result.term); +}