| author | |
| committer | |
| log | 2d6520d5d4cd912cdd20db6cfa155e871efc6f59 |
| tree | 583e47f4af61d3950b34333d48e738733b5e107a |
| parent | ac3946620cebfc706255a86103a14c6cb08cff97 |
| signature |
Previously, `zig fmt` on the stage1 compiler (which is what we currently
ship) would perform what equates to `zig run std/special/fmt_runner.zig`
Now, `zig fmt` is implemented with the hybrid zig/C++ strategy outlined
by #1964.
This means Zig no longer has to ship some of the stage2 .zig files, and
there is no longer a delay when running `zig fmt` for the first time.6 files changed, 284 insertions(+), 301 deletions(-)
CMakeLists.txt-5| ... | ... | @@ -685,7 +685,6 @@ set(ZIG_STD_FILES |
| 685 | 685 | "special/compiler_rt/udivmodti4.zig" |
| 686 | 686 | "special/compiler_rt/udivti3.zig" |
| 687 | 687 | "special/compiler_rt/umodti3.zig" |
| 688 | "special/fmt_runner.zig" | |
| 689 | 688 | "special/init-exe/build.zig" |
| 690 | 689 | "special/init-exe/src/main.zig" |
| 691 | 690 | "special/init-lib/build.zig" |
| ... | ... | @@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES}) |
| 6744 | 6743 | get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY) |
| 6745 | 6744 | install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}") |
| 6746 | 6745 | endforeach() |
| 6747 | ||
| 6748 | install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/arg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/") | |
| 6749 | install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/main.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/") | |
| 6750 | install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/errmsg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/") |
src-self-hosted/stage1.zig+275| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | // The prototypes in src/userland.h must match these definitions. |
| 3 | 3 | |
| 4 | 4 | const std = @import("std"); |
| 5 | const builtin = @import("builtin"); | |
| 5 | 6 | |
| 6 | 7 | // ABI warning |
| 7 | 8 | export fn stage2_zen(ptr: *[*]const u8, len: *usize) void { |
| ... | ... | @@ -119,3 +120,277 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { |
| 119 | 120 | }; |
| 120 | 121 | return Error.None; |
| 121 | 122 | } |
| 123 | ||
| 124 | // TODO: just use the actual self-hosted zig fmt. Until the coroutine rewrite, we use a blocking implementation. | |
| 125 | export fn stage2_fmt(argc: c_int, argv: [*]const [*]const u8) c_int { | |
| 126 | if (std.debug.runtime_safety) { | |
| 127 | fmtMain(argc, argv) catch unreachable; | |
| 128 | } else { | |
| 129 | fmtMain(argc, argv) catch |e| { | |
| 130 | std.debug.warn("{}\n", @errorName(e)); | |
| 131 | return -1; | |
| 132 | } | |
| 133 | } | |
| 134 | return 0; | |
| 135 | } | |
| 136 | ||
| 137 | fn fmtMain(argc: c_int, argv: [*]const [*]const u8) !void { | |
| 138 | const allocator = std.heap.c_allocator; | |
| 139 | var args_list = std.ArrayList([]const u8).init(allocator); | |
| 140 | const argc_usize = @intCast(usize, argc); | |
| 141 | var arg_i: usize = 0; | |
| 142 | while (arg_i < argc_usize) : (arg_i += 1) { | |
| 143 | try args_list.append(std.mem.toSliceConst(u8, argv[arg_i])); | |
| 144 | } | |
| 145 | ||
| 146 | var stdout_file = try std.io.getStdOut(); | |
| 147 | var stdout_out_stream = stdout_file.outStream(); | |
| 148 | stdout = &stdout_out_stream.stream; | |
| 149 | ||
| 150 | stderr_file = try std.io.getStdErr(); | |
| 151 | var stderr_out_stream = stderr_file.outStream(); | |
| 152 | stderr = &stderr_out_stream.stream; | |
| 153 | ||
| 154 | const args = args_list.toSliceConst(); | |
| 155 | var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[2..]); | |
| 156 | defer flags.deinit(); | |
| 157 | ||
| 158 | if (flags.present("help")) { | |
| 159 | try stdout.write(self_hosted_main.usage_fmt); | |
| 160 | os.exit(0); | |
| 161 | } | |
| 162 | ||
| 163 | const color = blk: { | |
| 164 | if (flags.single("color")) |color_flag| { | |
| 165 | if (mem.eql(u8, color_flag, "auto")) { | |
| 166 | break :blk errmsg.Color.Auto; | |
| 167 | } else if (mem.eql(u8, color_flag, "on")) { | |
| 168 | break :blk errmsg.Color.On; | |
| 169 | } else if (mem.eql(u8, color_flag, "off")) { | |
| 170 | break :blk errmsg.Color.Off; | |
| 171 | } else unreachable; | |
| 172 | } else { | |
| 173 | break :blk errmsg.Color.Auto; | |
| 174 | } | |
| 175 | }; | |
| 176 | ||
| 177 | if (flags.present("stdin")) { | |
| 178 | if (flags.positionals.len != 0) { | |
| 179 | try stderr.write("cannot use --stdin with positional arguments\n"); | |
| 180 | os.exit(1); | |
| 181 | } | |
| 182 | ||
| 183 | var stdin_file = try io.getStdIn(); | |
| 184 | var stdin = stdin_file.inStream(); | |
| 185 | ||
| 186 | const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size); | |
| 187 | defer allocator.free(source_code); | |
| 188 | ||
| 189 | const tree = std.zig.parse(allocator, source_code) catch |err| { | |
| 190 | try stderr.print("error parsing stdin: {}\n", err); | |
| 191 | os.exit(1); | |
| 192 | }; | |
| 193 | defer tree.deinit(); | |
| 194 | ||
| 195 | var error_it = tree.errors.iterator(0); | |
| 196 | while (error_it.next()) |parse_error| { | |
| 197 | try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color); | |
| 198 | } | |
| 199 | if (tree.errors.len != 0) { | |
| 200 | os.exit(1); | |
| 201 | } | |
| 202 | if (flags.present("check")) { | |
| 203 | const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); | |
| 204 | const code = if (anything_changed) u8(1) else u8(0); | |
| 205 | os.exit(code); | |
| 206 | } | |
| 207 | ||
| 208 | _ = try std.zig.render(allocator, stdout, tree); | |
| 209 | return; | |
| 210 | } | |
| 211 | ||
| 212 | if (flags.positionals.len == 0) { | |
| 213 | try stderr.write("expected at least one source file argument\n"); | |
| 214 | os.exit(1); | |
| 215 | } | |
| 216 | ||
| 217 | var fmt = Fmt{ | |
| 218 | .seen = Fmt.SeenMap.init(allocator), | |
| 219 | .any_error = false, | |
| 220 | .color = color, | |
| 221 | .allocator = allocator, | |
| 222 | }; | |
| 223 | ||
| 224 | const check_mode = flags.present("check"); | |
| 225 | ||
| 226 | for (flags.positionals.toSliceConst()) |file_path| { | |
| 227 | try fmtPath(&fmt, file_path, check_mode); | |
| 228 | } | |
| 229 | if (fmt.any_error) { | |
| 230 | os.exit(1); | |
| 231 | } | |
| 232 | } | |
| 233 | ||
| 234 | const FmtError = error{ | |
| 235 | SystemResources, | |
| 236 | OperationAborted, | |
| 237 | IoPending, | |
| 238 | BrokenPipe, | |
| 239 | Unexpected, | |
| 240 | WouldBlock, | |
| 241 | FileClosed, | |
| 242 | DestinationAddressRequired, | |
| 243 | DiskQuota, | |
| 244 | FileTooBig, | |
| 245 | InputOutput, | |
| 246 | NoSpaceLeft, | |
| 247 | AccessDenied, | |
| 248 | OutOfMemory, | |
| 249 | RenameAcrossMountPoints, | |
| 250 | ReadOnlyFileSystem, | |
| 251 | LinkQuotaExceeded, | |
| 252 | FileBusy, | |
| 253 | } || os.File.OpenError; | |
| 254 | ||
| 255 | fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { | |
| 256 | const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); | |
| 257 | defer fmt.allocator.free(file_path); | |
| 258 | ||
| 259 | if (try fmt.seen.put(file_path, {})) |_| return; | |
| 260 | ||
| 261 | const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { | |
| 262 | error.IsDir, error.AccessDenied => { | |
| 263 | // TODO make event based (and dir.next()) | |
| 264 | var dir = try std.os.Dir.open(fmt.allocator, file_path); | |
| 265 | defer dir.close(); | |
| 266 | ||
| 267 | while (try dir.next()) |entry| { | |
| 268 | if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { | |
| 269 | const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); | |
| 270 | try fmtPath(fmt, full_path, check_mode); | |
| 271 | } | |
| 272 | } | |
| 273 | return; | |
| 274 | }, | |
| 275 | else => { | |
| 276 | // TODO lock stderr printing | |
| 277 | try stderr.print("unable to open '{}': {}\n", file_path, err); | |
| 278 | fmt.any_error = true; | |
| 279 | return; | |
| 280 | }, | |
| 281 | }; | |
| 282 | defer fmt.allocator.free(source_code); | |
| 283 | ||
| 284 | const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { | |
| 285 | try stderr.print("error parsing file '{}': {}\n", file_path, err); | |
| 286 | fmt.any_error = true; | |
| 287 | return; | |
| 288 | }; | |
| 289 | defer tree.deinit(); | |
| 290 | ||
| 291 | var error_it = tree.errors.iterator(0); | |
| 292 | while (error_it.next()) |parse_error| { | |
| 293 | try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color); | |
| 294 | } | |
| 295 | if (tree.errors.len != 0) { | |
| 296 | fmt.any_error = true; | |
| 297 | return; | |
| 298 | } | |
| 299 | ||
| 300 | if (check_mode) { | |
| 301 | const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); | |
| 302 | if (anything_changed) { | |
| 303 | try stderr.print("{}\n", file_path); | |
| 304 | fmt.any_error = true; | |
| 305 | } | |
| 306 | } else { | |
| 307 | const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); | |
| 308 | defer baf.destroy(); | |
| 309 | ||
| 310 | const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); | |
| 311 | if (anything_changed) { | |
| 312 | try stderr.print("{}\n", file_path); | |
| 313 | try baf.finish(); | |
| 314 | } | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | const Fmt = struct { | |
| 319 | seen: SeenMap, | |
| 320 | any_error: bool, | |
| 321 | color: errmsg.Color, | |
| 322 | allocator: *mem.Allocator, | |
| 323 | ||
| 324 | const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); | |
| 325 | }; | |
| 326 | ||
| 327 | fn printErrMsgToFile( | |
| 328 | allocator: *mem.Allocator, | |
| 329 | parse_error: *const ast.Error, | |
| 330 | tree: *ast.Tree, | |
| 331 | path: []const u8, | |
| 332 | file: os.File, | |
| 333 | color: errmsg.Color, | |
| 334 | ) !void { | |
| 335 | const color_on = switch (color) { | |
| 336 | errmsg.Color.Auto => file.isTty(), | |
| 337 | errmsg.Color.On => true, | |
| 338 | errmsg.Color.Off => false, | |
| 339 | }; | |
| 340 | const lok_token = parse_error.loc(); | |
| 341 | const span = errmsg.Span{ | |
| 342 | .first = lok_token, | |
| 343 | .last = lok_token, | |
| 344 | }; | |
| 345 | ||
| 346 | const first_token = tree.tokens.at(span.first); | |
| 347 | const last_token = tree.tokens.at(span.last); | |
| 348 | const start_loc = tree.tokenLocationPtr(0, first_token); | |
| 349 | const end_loc = tree.tokenLocationPtr(first_token.end, last_token); | |
| 350 | ||
| 351 | var text_buf = try std.Buffer.initSize(allocator, 0); | |
| 352 | var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; | |
| 353 | try parse_error.render(&tree.tokens, out_stream); | |
| 354 | const text = text_buf.toOwnedSlice(); | |
| 355 | ||
| 356 | const stream = &file.outStream().stream; | |
| 357 | if (!color_on) { | |
| 358 | try stream.print( | |
| 359 | "{}:{}:{}: error: {}\n", | |
| 360 | path, | |
| 361 | start_loc.line + 1, | |
| 362 | start_loc.column + 1, | |
| 363 | text, | |
| 364 | ); | |
| 365 | return; | |
| 366 | } | |
| 367 | ||
| 368 | try stream.print( | |
| 369 | "{}:{}:{}: error: {}\n{}\n", | |
| 370 | path, | |
| 371 | start_loc.line + 1, | |
| 372 | start_loc.column + 1, | |
| 373 | text, | |
| 374 | tree.source[start_loc.line_start..start_loc.line_end], | |
| 375 | ); | |
| 376 | try stream.writeByteNTimes(' ', start_loc.column); | |
| 377 | try stream.writeByteNTimes('~', last_token.end - first_token.start); | |
| 378 | try stream.write("\n"); | |
| 379 | } | |
| 380 | ||
| 381 | const os = std.os; | |
| 382 | const io = std.io; | |
| 383 | const mem = std.mem; | |
| 384 | const Allocator = mem.Allocator; | |
| 385 | const ArrayList = std.ArrayList; | |
| 386 | const Buffer = std.Buffer; | |
| 387 | ||
| 388 | const arg = @import("arg.zig"); | |
| 389 | const self_hosted_main = @import("main.zig"); | |
| 390 | const Args = arg.Args; | |
| 391 | const Flag = arg.Flag; | |
| 392 | const errmsg = @import("errmsg.zig"); | |
| 393 | ||
| 394 | var stderr_file: os.File = undefined; | |
| 395 | var stderr: *io.OutStream(os.File.WriteError) = undefined; | |
| 396 | var stdout: *io.OutStream(os.File.WriteError) = undefined; |
src/main.cpp+1-31| ... | ... | @@ -588,37 +588,7 @@ int main(int argc, char **argv) { |
| 588 | 588 | } |
| 589 | 589 | return (term.how == TerminationIdClean) ? term.code : -1; |
| 590 | 590 | } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) { |
| 591 | init_all_targets(); | |
| 592 | ZigTarget target; | |
| 593 | get_native_target(&target); | |
| 594 | Buf *zig_lib_dir = (override_lib_dir == nullptr) ? get_zig_lib_dir() : override_lib_dir; | |
| 595 | Buf *fmt_runner_path = buf_alloc(); | |
| 596 | os_path_join(get_zig_special_dir(zig_lib_dir), buf_create_from_str("fmt_runner.zig"), fmt_runner_path); | |
| 597 | Buf *cache_dir_buf = buf_create_from_str(cache_dir ? cache_dir : default_zig_cache_name); | |
| 598 | CodeGen *g = codegen_create(main_pkg_path, fmt_runner_path, &target, OutTypeExe, | |
| 599 | BuildModeDebug, zig_lib_dir, nullptr, nullptr, cache_dir_buf); | |
| 600 | g->valgrind_support = valgrind_support; | |
| 601 | g->want_single_threaded = true; | |
| 602 | codegen_set_out_name(g, buf_create_from_str("fmt")); | |
| 603 | g->enable_cache = true; | |
| 604 | ||
| 605 | codegen_build_and_link(g); | |
| 606 | ||
| 607 | // TODO standardize os.cpp so that the args are supposed to have the exe | |
| 608 | ZigList<const char*> args_with_exe = {0}; | |
| 609 | ZigList<const char*> args_without_exe = {0}; | |
| 610 | const char *exec_path = buf_ptr(&g->output_file_path); | |
| 611 | args_with_exe.append(exec_path); | |
| 612 | for (int i = 2; i < argc; i += 1) { | |
| 613 | args_with_exe.append(argv[i]); | |
| 614 | args_without_exe.append(argv[i]); | |
| 615 | } | |
| 616 | args_with_exe.append(nullptr); | |
| 617 | os_execv(exec_path, args_with_exe.items); | |
| 618 | ||
| 619 | Termination term; | |
| 620 | os_spawn_process(exec_path, args_without_exe, &term); | |
| 621 | return term.code; | |
| 591 | return stage2_fmt(argc, argv); | |
| 622 | 592 | } |
| 623 | 593 | |
| 624 | 594 | for (int i = 1; i < argc; i += 1) { |
src/userland.cpp+5| ... | ... | @@ -37,3 +37,8 @@ void stage2_render_ast(struct Stage2Ast *ast, FILE *output_file) { |
| 37 | 37 | const char *msg = "stage0 called stage2_render_ast"; |
| 38 | 38 | stage2_panic(msg, strlen(msg)); |
| 39 | 39 | } |
| 40 | ||
| 41 | int stage2_fmt(int argc, char **argv) { | |
| 42 | const char *msg = "stage0 called stage2_fmt"; | |
| 43 | stage2_panic(msg, strlen(msg)); | |
| 44 | } |
src/userland.h+3| ... | ... | @@ -114,4 +114,7 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len); |
| 114 | 114 | // ABI warning |
| 115 | 115 | ZIG_EXTERN_C ZIG_ATTRIBUTE_NORETURN void stage2_panic(const char *ptr, size_t len); |
| 116 | 116 | |
| 117 | // ABI warning | |
| 118 | ZIG_EXTERN_C int stage2_fmt(int argc, char **argv); | |
| 119 | ||
| 117 | 120 | #endif |
std/special/fmt_runner.zig deleted-265| ... | ... | @@ -1,265 +0,0 @@ |
| 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[1..]); | |
| 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 | const 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 | var fmt = Fmt{ | |
| 103 | .seen = Fmt.SeenMap.init(allocator), | |
| 104 | .any_error = false, | |
| 105 | .color = color, | |
| 106 | .allocator = allocator, | |
| 107 | }; | |
| 108 | ||
| 109 | const check_mode = flags.present("check"); | |
| 110 | ||
| 111 | for (flags.positionals.toSliceConst()) |file_path| { | |
| 112 | try fmtPath(&fmt, file_path, check_mode); | |
| 113 | } | |
| 114 | if (fmt.any_error) { | |
| 115 | os.exit(1); | |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | const FmtError = error{ | |
| 120 | SystemResources, | |
| 121 | OperationAborted, | |
| 122 | IoPending, | |
| 123 | BrokenPipe, | |
| 124 | Unexpected, | |
| 125 | WouldBlock, | |
| 126 | FileClosed, | |
| 127 | DestinationAddressRequired, | |
| 128 | DiskQuota, | |
| 129 | FileTooBig, | |
| 130 | InputOutput, | |
| 131 | NoSpaceLeft, | |
| 132 | AccessDenied, | |
| 133 | OutOfMemory, | |
| 134 | RenameAcrossMountPoints, | |
| 135 | ReadOnlyFileSystem, | |
| 136 | LinkQuotaExceeded, | |
| 137 | FileBusy, | |
| 138 | } || os.File.OpenError; | |
| 139 | ||
| 140 | fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { | |
| 141 | const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); | |
| 142 | defer fmt.allocator.free(file_path); | |
| 143 | ||
| 144 | if (try fmt.seen.put(file_path, {})) |_| return; | |
| 145 | ||
| 146 | const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { | |
| 147 | error.IsDir, error.AccessDenied => { | |
| 148 | // TODO make event based (and dir.next()) | |
| 149 | var dir = try std.os.Dir.open(fmt.allocator, file_path); | |
| 150 | defer dir.close(); | |
| 151 | ||
| 152 | while (try dir.next()) |entry| { | |
| 153 | if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { | |
| 154 | const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); | |
| 155 | try fmtPath(fmt, full_path, check_mode); | |
| 156 | } | |
| 157 | } | |
| 158 | return; | |
| 159 | }, | |
| 160 | else => { | |
| 161 | // TODO lock stderr printing | |
| 162 | try stderr.print("unable to open '{}': {}\n", file_path, err); | |
| 163 | fmt.any_error = true; | |
| 164 | return; | |
| 165 | }, | |
| 166 | }; | |
| 167 | defer fmt.allocator.free(source_code); | |
| 168 | ||
| 169 | const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { | |
| 170 | try stderr.print("error parsing file '{}': {}\n", file_path, err); | |
| 171 | fmt.any_error = true; | |
| 172 | return; | |
| 173 | }; | |
| 174 | defer tree.deinit(); | |
| 175 | ||
| 176 | var error_it = tree.errors.iterator(0); | |
| 177 | while (error_it.next()) |parse_error| { | |
| 178 | try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color); | |
| 179 | } | |
| 180 | if (tree.errors.len != 0) { | |
| 181 | fmt.any_error = true; | |
| 182 | return; | |
| 183 | } | |
| 184 | ||
| 185 | if (check_mode) { | |
| 186 | const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); | |
| 187 | if (anything_changed) { | |
| 188 | try stderr.print("{}\n", file_path); | |
| 189 | fmt.any_error = true; | |
| 190 | } | |
| 191 | } else { | |
| 192 | // TODO make this evented | |
| 193 | const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); | |
| 194 | defer baf.destroy(); | |
| 195 | ||
| 196 | const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); | |
| 197 | if (anything_changed) { | |
| 198 | try stderr.print("{}\n", file_path); | |
| 199 | try baf.finish(); | |
| 200 | } | |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | const Fmt = struct { | |
| 205 | seen: SeenMap, | |
| 206 | any_error: bool, | |
| 207 | color: errmsg.Color, | |
| 208 | allocator: *mem.Allocator, | |
| 209 | ||
| 210 | const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); | |
| 211 | }; | |
| 212 | ||
| 213 | fn printErrMsgToFile( | |
| 214 | allocator: *mem.Allocator, | |
| 215 | parse_error: *const ast.Error, | |
| 216 | tree: *ast.Tree, | |
| 217 | path: []const u8, | |
| 218 | file: os.File, | |
| 219 | color: errmsg.Color, | |
| 220 | ) !void { | |
| 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 | } |