|
| 1 | +const std = @import("std"); |
| 2 | +const builtin = @import("builtin"); |
| 3 | + |
| 4 | +const os = std.os; |
| 5 | +const io = std.io; |
| 6 | +const mem = std.mem; |
| 7 | +const Allocator = mem.Allocator; |
| 8 | +const ArrayList = std.ArrayList; |
| 9 | +const Buffer = std.Buffer; |
| 10 | +const ast = std.zig.ast; |
| 11 | + |
| 12 | +const arg = @import("fmt/arg.zig"); |
| 13 | +const self_hosted_main = @import("fmt/main.zig"); |
| 14 | +const Args = arg.Args; |
| 15 | +const Flag = arg.Flag; |
| 16 | +const errmsg = @import("fmt/errmsg.zig"); |
| 17 | + |
| 18 | +var stderr_file: os.File = undefined; |
| 19 | +var stderr: *io.OutStream(os.File.WriteError) = undefined; |
| 20 | +var stdout: *io.OutStream(os.File.WriteError) = undefined; |
| 21 | + |
| 22 | +// This brings `zig fmt` to stage 1. |
| 23 | +pub fn main() !void { |
| 24 | + // Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived, |
| 25 | + // one shot program. We don't need to waste time freeing memory and finding places to squish |
| 26 | + // bytes into. So we free everything all at once at the very end. |
| 27 | + var direct_allocator = std.heap.DirectAllocator.init(); |
| 28 | + var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator); |
| 29 | + const allocator = &arena.allocator; |
| 30 | + |
| 31 | + var stdout_file = try std.io.getStdOut(); |
| 32 | + var stdout_out_stream = stdout_file.outStream(); |
| 33 | + stdout = &stdout_out_stream.stream; |
| 34 | + |
| 35 | + stderr_file = try std.io.getStdErr(); |
| 36 | + var stderr_out_stream = stderr_file.outStream(); |
| 37 | + stderr = &stderr_out_stream.stream; |
| 38 | + const args = try std.os.argsAlloc(allocator); |
| 39 | + |
| 40 | + var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args); |
| 41 | + defer flags.deinit(); |
| 42 | + |
| 43 | + if (flags.present("help")) { |
| 44 | + try stdout.write(self_hosted_main.usage_fmt); |
| 45 | + os.exit(0); |
| 46 | + } |
| 47 | + |
| 48 | + const color = blk: { |
| 49 | + if (flags.single("color")) |color_flag| { |
| 50 | + if (mem.eql(u8, color_flag, "auto")) { |
| 51 | + break :blk errmsg.Color.Auto; |
| 52 | + } else if (mem.eql(u8, color_flag, "on")) { |
| 53 | + break :blk errmsg.Color.On; |
| 54 | + } else if (mem.eql(u8, color_flag, "off")) { |
| 55 | + break :blk errmsg.Color.Off; |
| 56 | + } else unreachable; |
| 57 | + } else { |
| 58 | + break :blk errmsg.Color.Auto; |
| 59 | + } |
| 60 | + }; |
| 61 | + |
| 62 | + if (flags.present("stdin")) { |
| 63 | + if (flags.positionals.len != 0) { |
| 64 | + try stderr.write("cannot use --stdin with positional arguments\n"); |
| 65 | + os.exit(1); |
| 66 | + } |
| 67 | + |
| 68 | + var stdin_file = try io.getStdIn(); |
| 69 | + var stdin = stdin_file.inStream(); |
| 70 | + |
| 71 | + const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size); |
| 72 | + defer allocator.free(source_code); |
| 73 | + |
| 74 | + var tree = std.zig.parse(allocator, source_code) catch |err| { |
| 75 | + try stderr.print("error parsing stdin: {}\n", err); |
| 76 | + os.exit(1); |
| 77 | + }; |
| 78 | + defer tree.deinit(); |
| 79 | + |
| 80 | + var error_it = tree.errors.iterator(0); |
| 81 | + while (error_it.next()) |parse_error| { |
| 82 | + try printErrMsgToFile(allocator, parse_error, &tree, "<stdin>", stderr_file, color); |
| 83 | + } |
| 84 | + if (tree.errors.len != 0) { |
| 85 | + os.exit(1); |
| 86 | + } |
| 87 | + if (flags.present("check")) { |
| 88 | + const anything_changed = try std.zig.render(allocator, io.null_out_stream, &tree); |
| 89 | + const code = if (anything_changed) u8(1) else u8(0); |
| 90 | + os.exit(code); |
| 91 | + } |
| 92 | + |
| 93 | + _ = try std.zig.render(allocator, stdout, &tree); |
| 94 | + return; |
| 95 | + } |
| 96 | + |
| 97 | + if (flags.positionals.len == 0) { |
| 98 | + try stderr.write("expected at least one source file argument\n"); |
| 99 | + os.exit(1); |
| 100 | + } |
| 101 | + |
| 102 | + if (flags.positionals.len == 0) { |
| 103 | + try stderr.write("expected at least one source file argument\n"); |
| 104 | + os.exit(1); |
| 105 | + } |
| 106 | + |
| 107 | + var fmt = Fmt{ |
| 108 | + .seen = Fmt.SeenMap.init(allocator), |
| 109 | + .any_error = false, |
| 110 | + .color = color, |
| 111 | + .allocator = allocator, |
| 112 | + }; |
| 113 | + |
| 114 | + const check_mode = flags.present("check"); |
| 115 | + |
| 116 | + for (flags.positionals.toSliceConst()) |file_path| { |
| 117 | + try fmtPath(&fmt, file_path, check_mode); |
| 118 | + } |
| 119 | + if (fmt.any_error) { |
| 120 | + os.exit(1); |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +const FmtError = error{ |
| 125 | + SystemResources, |
| 126 | + OperationAborted, |
| 127 | + IoPending, |
| 128 | + BrokenPipe, |
| 129 | + Unexpected, |
| 130 | + WouldBlock, |
| 131 | + FileClosed, |
| 132 | + DestinationAddressRequired, |
| 133 | + DiskQuota, |
| 134 | + FileTooBig, |
| 135 | + InputOutput, |
| 136 | + NoSpaceLeft, |
| 137 | + AccessDenied, |
| 138 | + OutOfMemory, |
| 139 | + RenameAcrossMountPoints, |
| 140 | + ReadOnlyFileSystem, |
| 141 | + LinkQuotaExceeded, |
| 142 | + FileBusy, |
| 143 | +} || os.File.OpenError; |
| 144 | + |
| 145 | +fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { |
| 146 | + const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); |
| 147 | + defer fmt.allocator.free(file_path); |
| 148 | + |
| 149 | + if (try fmt.seen.put(file_path, {})) |_| return; |
| 150 | + |
| 151 | + const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { |
| 152 | + error.IsDir, error.AccessDenied => { |
| 153 | + // TODO make event based (and dir.next()) |
| 154 | + var dir = try std.os.Dir.open(fmt.allocator, file_path); |
| 155 | + defer dir.close(); |
| 156 | + |
| 157 | + while (try dir.next()) |entry| { |
| 158 | + if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { |
| 159 | + const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); |
| 160 | + try fmtPath(fmt, full_path, check_mode); |
| 161 | + } |
| 162 | + } |
| 163 | + return; |
| 164 | + }, |
| 165 | + else => { |
| 166 | + // TODO lock stderr printing |
| 167 | + try stderr.print("unable to open '{}': {}\n", file_path, err); |
| 168 | + fmt.any_error = true; |
| 169 | + return; |
| 170 | + }, |
| 171 | + }; |
| 172 | + defer fmt.allocator.free(source_code); |
| 173 | + |
| 174 | + var tree = std.zig.parse(fmt.allocator, source_code) catch |err| { |
| 175 | + try stderr.print("error parsing file '{}': {}\n", file_path, err); |
| 176 | + fmt.any_error = true; |
| 177 | + return; |
| 178 | + }; |
| 179 | + defer tree.deinit(); |
| 180 | + |
| 181 | + var error_it = tree.errors.iterator(0); |
| 182 | + while (error_it.next()) |parse_error| { |
| 183 | + try printErrMsgToFile(fmt.allocator, parse_error, &tree, file_path, stderr_file, fmt.color); |
| 184 | + } |
| 185 | + if (tree.errors.len != 0) { |
| 186 | + fmt.any_error = true; |
| 187 | + return; |
| 188 | + } |
| 189 | + |
| 190 | + if (check_mode) { |
| 191 | + const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, &tree); |
| 192 | + if (anything_changed) { |
| 193 | + try stderr.print("{}\n", file_path); |
| 194 | + fmt.any_error = true; |
| 195 | + } |
| 196 | + } else { |
| 197 | + // TODO make this evented |
| 198 | + const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); |
| 199 | + defer baf.destroy(); |
| 200 | + |
| 201 | + const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), &tree); |
| 202 | + if (anything_changed) { |
| 203 | + try stderr.print("{}\n", file_path); |
| 204 | + try baf.finish(); |
| 205 | + } |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +const Fmt = struct { |
| 210 | + seen: SeenMap, |
| 211 | + any_error: bool, |
| 212 | + color: errmsg.Color, |
| 213 | + allocator: *mem.Allocator, |
| 214 | + |
| 215 | + const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); |
| 216 | +}; |
| 217 | + |
| 218 | +fn printErrMsgToFile(allocator: *mem.Allocator, parse_error: *const ast.Error, tree: *ast.Tree, |
| 219 | + path: []const u8, file: os.File, color: errmsg.Color,) !void |
| 220 | +{ |
| 221 | + const color_on = switch (color) { |
| 222 | + errmsg.Color.Auto => file.isTty(), |
| 223 | + errmsg.Color.On => true, |
| 224 | + errmsg.Color.Off => false, |
| 225 | + }; |
| 226 | + const lok_token = parse_error.loc(); |
| 227 | + const span = errmsg.Span{ |
| 228 | + .first = lok_token, |
| 229 | + .last = lok_token, |
| 230 | + }; |
| 231 | + |
| 232 | + const first_token = tree.tokens.at(span.first); |
| 233 | + const last_token = tree.tokens.at(span.last); |
| 234 | + const start_loc = tree.tokenLocationPtr(0, first_token); |
| 235 | + const end_loc = tree.tokenLocationPtr(first_token.end, last_token); |
| 236 | + |
| 237 | + var text_buf = try std.Buffer.initSize(allocator, 0); |
| 238 | + var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; |
| 239 | + try parse_error.render(&tree.tokens, out_stream); |
| 240 | + const text = text_buf.toOwnedSlice(); |
| 241 | + |
| 242 | + const stream = &file.outStream().stream; |
| 243 | + if (!color_on) { |
| 244 | + try stream.print( |
| 245 | + "{}:{}:{}: error: {}\n", |
| 246 | + path, |
| 247 | + start_loc.line + 1, |
| 248 | + start_loc.column + 1, |
| 249 | + text, |
| 250 | + ); |
| 251 | + return; |
| 252 | + } |
| 253 | + |
| 254 | + try stream.print( |
| 255 | + "{}:{}:{}: error: {}\n{}\n", |
| 256 | + path, |
| 257 | + start_loc.line + 1, |
| 258 | + start_loc.column + 1, |
| 259 | + text, |
| 260 | + tree.source[start_loc.line_start..start_loc.line_end], |
| 261 | + ); |
| 262 | + try stream.writeByteNTimes(' ', start_loc.column); |
| 263 | + try stream.writeByteNTimes('~', last_token.end - first_token.start); |
| 264 | + try stream.write("\n"); |
| 265 | +} |
0 commit comments