| ... | ... | @@ -0,0 +1,265 @@ |
| 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 | } |