| ... | @@ -1,13 +1,34 @@ | ... | @@ -1,13 +1,34 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| | 2 | const mem = std.mem; |
| 2 | const Allocator = std.mem.Allocator; | 3 | const Allocator = std.mem.Allocator; |
| 3 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| | 5 | const fatal = @import("./main.zig").fatal; |
| 4 | | 6 | |
| 5 | const usage = | 7 | const usage = |
| 6 | \\zig reduce [source_file] [interestingness] | 8 | \\zig reduce [options] ./checker root_source_file.zig [-- [argv]] |
| | 9 | \\ |
| | 10 | \\root_source_file.zig is relative to --main-mod-path. |
| | 11 | \\ |
| | 12 | \\checker: |
| | 13 | \\ An executable that communicates interestingness by returning these exit codes: |
| | 14 | \\ exit(0): interesting |
| | 15 | \\ exit(1): unknown (infinite loop or other mishap) |
| | 16 | \\ exit(other): not interesting |
| | 17 | \\ |
| | 18 | \\options: |
| | 19 | \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name |
| | 20 | \\ deps: [dep],[dep],... |
| | 21 | \\ dep: [[import=]name] |
| | 22 | \\ --deps [dep],[dep],... Set dependency names for the root package |
| | 23 | \\ dep: [[import=]name] |
| | 24 | \\ --main-mod-path Set the directory of the root module |
| | 25 | \\ |
| | 26 | \\argv: |
| | 27 | \\ Forwarded directly to the interestingness script. |
| 7 | \\ | 28 | \\ |
| 8 | ; | 29 | ; |
| 9 | | 30 | |
| 10 | const Interestingness = enum { interesting, boring, unknown }; | 31 | const Interestingness = enum { interesting, unknown, boring }; |
| 11 | | 32 | |
| 12 | // Roadmap: | 33 | // Roadmap: |
| 13 | // - add thread pool | 34 | // - add thread pool |
| ... | @@ -22,13 +43,44 @@ const Interestingness = enum { interesting, boring, unknown }; | ... | @@ -22,13 +43,44 @@ const Interestingness = enum { interesting, boring, unknown }; |
| 22 | // - integrate the build system? | 43 | // - integrate the build system? |
| 23 | | 44 | |
| 24 | pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | 45 | pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 25 | const file_path = args[2]; | 46 | var opt_checker_path: ?[]const u8 = null; |
| 26 | const interestingness_argv_template = args[3..]; | 47 | var opt_root_source_file_path: ?[]const u8 = null; |
| | 48 | var argv: []const []const u8 = &.{}; |
| | 49 | |
| | 50 | { |
| | 51 | var i: usize = 2; // skip over "zig" and "reduce" |
| | 52 | while (i < args.len) : (i += 1) { |
| | 53 | const arg = args[i]; |
| | 54 | if (mem.startsWith(u8, arg, "-")) { |
| | 55 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| | 56 | const stdout = std.io.getStdOut().writer(); |
| | 57 | try stdout.writeAll(usage); |
| | 58 | return std.process.cleanExit(); |
| | 59 | } else if (mem.eql(u8, arg, "--")) { |
| | 60 | argv = args[i + 1 ..]; |
| | 61 | break; |
| | 62 | } else { |
| | 63 | fatal("unrecognized parameter: '{s}'", .{arg}); |
| | 64 | } |
| | 65 | } else if (opt_checker_path == null) { |
| | 66 | opt_checker_path = arg; |
| | 67 | } else if (opt_root_source_file_path == null) { |
| | 68 | opt_root_source_file_path = arg; |
| | 69 | } else { |
| | 70 | fatal("unexpected extra parameter: '{s}'", .{arg}); |
| | 71 | } |
| | 72 | } |
| | 73 | } |
| | 74 | |
| | 75 | const checker_path = opt_checker_path orelse |
| | 76 | fatal("missing interestingness checker argument; see -h for usage", .{}); |
| | 77 | const root_source_file_path = opt_root_source_file_path orelse |
| | 78 | fatal("missing root source file path argument; see -h for usage", .{}); |
| 27 | | 79 | |
| 28 | var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{}; | 80 | var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{}; |
| 29 | try interestingness_argv.ensureUnusedCapacity(arena, interestingness_argv_template.len + 1); | 81 | try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1); |
| 30 | interestingness_argv.appendSliceAssumeCapacity(interestingness_argv_template); | 82 | interestingness_argv.appendAssumeCapacity(checker_path); |
| 31 | interestingness_argv.appendAssumeCapacity(file_path); | 83 | interestingness_argv.appendSliceAssumeCapacity(argv); |
| 32 | | 84 | |
| 33 | var rendered = std.ArrayList(u8).init(gpa); | 85 | var rendered = std.ArrayList(u8).init(gpa); |
| 34 | defer rendered.deinit(); | 86 | defer rendered.deinit(); |
| ... | @@ -38,7 +90,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | ... | @@ -38,7 +90,7 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 38 | | 90 | |
| 39 | const source_code = try std.fs.cwd().readFileAllocOptions( | 91 | const source_code = try std.fs.cwd().readFileAllocOptions( |
| 40 | arena, | 92 | arena, |
| 41 | file_path, | 93 | root_source_file_path, |
| 42 | std.math.maxInt(u32), | 94 | std.math.maxInt(u32), |
| 43 | null, | 95 | null, |
| 44 | 1, | 96 | 1, |
| ... | @@ -55,38 +107,34 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | ... | @@ -55,38 +107,34 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 55 | var next_gut_fn_index: u32 = 0; | 107 | var next_gut_fn_index: u32 = 0; |
| 56 | var fixups: std.zig.Ast.Fixups = .{}; | 108 | var fixups: std.zig.Ast.Fixups = .{}; |
| 57 | | 109 | |
| | 110 | { |
| | 111 | // smoke test the interestingness check |
| | 112 | switch (try runCheck(arena, interestingness_argv.items)) { |
| | 113 | .interesting => {}, |
| | 114 | .boring, .unknown => |t| { |
| | 115 | fatal("interestingness check returned {s} for unmodified input\n", .{ |
| | 116 | @tagName(t), |
| | 117 | }); |
| | 118 | }, |
| | 119 | } |
| | 120 | } |
| | 121 | |
| 58 | while (true) { | 122 | while (true) { |
| 59 | try fixups.gut_functions.put(arena, next_gut_fn_index, {}); | 123 | try fixups.gut_functions.put(arena, next_gut_fn_index, {}); |
| 60 | | 124 | |
| 61 | rendered.clearRetainingCapacity(); | 125 | rendered.clearRetainingCapacity(); |
| 62 | try tree.renderToArrayList(&rendered, fixups); | 126 | try tree.renderToArrayList(&rendered, fixups); |
| 63 | | 127 | |
| 64 | if (std.mem.eql(u8, rendered.items, prev_rendered.items)) { | 128 | if (mem.eql(u8, rendered.items, prev_rendered.items)) { |
| 65 | std.debug.print("no remaining transformations\n", .{}); | 129 | std.debug.print("no remaining transformations\n", .{}); |
| 66 | break; | 130 | break; |
| 67 | } | 131 | } |
| 68 | prev_rendered.clearRetainingCapacity(); | 132 | prev_rendered.clearRetainingCapacity(); |
| 69 | try prev_rendered.appendSlice(rendered.items); | 133 | try prev_rendered.appendSlice(rendered.items); |
| 70 | | 134 | |
| 71 | try std.fs.cwd().writeFile(file_path, rendered.items); | 135 | try std.fs.cwd().writeFile(root_source_file_path, rendered.items); |
| 72 | | 136 | |
| 73 | const result = try std.process.Child.run(.{ | 137 | const interestingness = try runCheck(arena, interestingness_argv.items); |
| 74 | .allocator = arena, | | |
| 75 | .argv = interestingness_argv.items, | | |
| 76 | }); | | |
| 77 | if (result.stderr.len != 0) | | |
| 78 | std.debug.print("{s}", .{result.stderr}); | | |
| 79 | const interestingness: Interestingness = switch (result.term) { | | |
| 80 | .Exited => |code| switch (code) { | | |
| 81 | 0 => .interesting, | | |
| 82 | 1 => .unknown, | | |
| 83 | else => .boring, | | |
| 84 | }, | | |
| 85 | else => b: { | | |
| 86 | std.debug.print("interestingness check aborted unexpectedly\n", .{}); | | |
| 87 | break :b .boring; | | |
| 88 | }, | | |
| 89 | }; | | |
| 90 | std.debug.print("{s}\n", .{@tagName(interestingness)}); | 138 | std.debug.print("{s}\n", .{@tagName(interestingness)}); |
| 91 | switch (interestingness) { | 139 | switch (interestingness) { |
| 92 | .interesting => { | 140 | .interesting => { |
| ... | @@ -104,3 +152,27 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { | ... | @@ -104,3 +152,27 @@ pub fn main(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { |
| 104 | } | 152 | } |
| 105 | return std.process.cleanExit(); | 153 | return std.process.cleanExit(); |
| 106 | } | 154 | } |
| | 155 | |
| | 156 | fn termToInteresting(term: std.process.Child.Term) Interestingness { |
| | 157 | return switch (term) { |
| | 158 | .Exited => |code| switch (code) { |
| | 159 | 0 => .interesting, |
| | 160 | 1 => .unknown, |
| | 161 | else => .boring, |
| | 162 | }, |
| | 163 | else => b: { |
| | 164 | std.debug.print("interestingness check aborted unexpectedly\n", .{}); |
| | 165 | break :b .boring; |
| | 166 | }, |
| | 167 | }; |
| | 168 | } |
| | 169 | |
| | 170 | fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness { |
| | 171 | const result = try std.process.Child.run(.{ |
| | 172 | .allocator = arena, |
| | 173 | .argv = argv, |
| | 174 | }); |
| | 175 | if (result.stderr.len != 0) |
| | 176 | std.debug.print("{s}", .{result.stderr}); |
| | 177 | return termToInteresting(result.term); |
| | 178 | } |