| author | |
| committer | |
| log | 2d6520d5d4cd912cdd20db6cfa155e871efc6f59 |
| tree | 583e47f4af61d3950b34333d48e738733b5e107a |
| parent | ac3946620cebfc706255a86103a14c6cb08cff97 |
| signature | Commit is signed but in an unrecognized format. |
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,7 +685,6 @@ set(ZIG_STD_FILES |
| 685 | "special/compiler_rt/udivmodti4.zig" | 685 | "special/compiler_rt/udivmodti4.zig" |
| 686 | "special/compiler_rt/udivti3.zig" | 686 | "special/compiler_rt/udivti3.zig" |
| 687 | "special/compiler_rt/umodti3.zig" | 687 | "special/compiler_rt/umodti3.zig" |
| 688 | "special/fmt_runner.zig" | ||
| 689 | "special/init-exe/build.zig" | 688 | "special/init-exe/build.zig" |
| 690 | "special/init-exe/src/main.zig" | 689 | "special/init-exe/src/main.zig" |
| 691 | "special/init-lib/build.zig" | 690 | "special/init-lib/build.zig" |
| ... | @@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES}) | ... | @@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES}) |
| 6744 | get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY) | 6743 | get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY) |
| 6745 | install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}") | 6744 | install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}") |
| 6746 | endforeach() | 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,6 +2,7 @@ |
| 2 | // The prototypes in src/userland.h must match these definitions. | 2 | // The prototypes in src/userland.h must match these definitions. |
| 3 | 3 | ||
| 4 | const std = @import("std"); | 4 | const std = @import("std"); |
| 5 | const builtin = @import("builtin"); | ||
| 5 | 6 | ||
| 6 | // ABI warning | 7 | // ABI warning |
| 7 | export fn stage2_zen(ptr: *[*]const u8, len: *usize) void { | 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,3 +120,277 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { |
| 119 | }; | 120 | }; |
| 120 | return Error.None; | 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,37 +588,7 @@ int main(int argc, char **argv) { |
| 588 | } | 588 | } |
| 589 | return (term.how == TerminationIdClean) ? term.code : -1; | 589 | return (term.how == TerminationIdClean) ? term.code : -1; |
| 590 | } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) { | 590 | } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) { |
| 591 | init_all_targets(); | 591 | return stage2_fmt(argc, argv); |
| 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; | ||
| 622 | } | 592 | } |
| 623 | 593 | ||
| 624 | for (int i = 1; i < argc; i += 1) { | 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,3 +37,8 @@ void stage2_render_ast(struct Stage2Ast *ast, FILE *output_file) { |
| 37 | const char *msg = "stage0 called stage2_render_ast"; | 37 | const char *msg = "stage0 called stage2_render_ast"; |
| 38 | stage2_panic(msg, strlen(msg)); | 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,4 +114,7 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len); |
| 114 | // ABI warning | 114 | // ABI warning |
| 115 | ZIG_EXTERN_C ZIG_ATTRIBUTE_NORETURN void stage2_panic(const char *ptr, size_t len); | 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 | #endif | 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 | } | ||