From ba56f365c813440b79c1710c6a8b0fd591883e13 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Feb 2019 00:42:56 -0500 Subject: [PATCH] bring zig fmt to stage1 --- CMakeLists.txt | 5 + src-self-hosted/main.zig | 6 +- src/main.cpp | 36 ++++- std/special/fmt_runner.zig | 265 +++++++++++++++++++++++++++++++++++++ 4 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 std/special/fmt_runner.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e80c65dbd..db5f50908c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -652,6 +652,7 @@ set(ZIG_STD_FILES "special/compiler_rt/udivmodti4.zig" "special/compiler_rt/udivti3.zig" "special/compiler_rt/umodti3.zig" + "special/fmt_runner.zig" "special/init-exe/build.zig" "special/init-exe/src/main.zig" "special/init-lib/build.zig" @@ -905,3 +906,7 @@ foreach(file ${ZIG_STD_FILES}) get_filename_component(file_dir "${ZIG_STD_DEST}/${file}" DIRECTORY) install(FILES "${CMAKE_SOURCE_DIR}/std/${file}" DESTINATION "${file_dir}") endforeach() + +install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/arg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/") +install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/main.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/") +install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/errmsg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/") diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 64aa729469..42556beaed 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -24,7 +24,7 @@ var stderr_file: os.File = undefined; var stderr: *io.OutStream(os.File.WriteError) = undefined; var stdout: *io.OutStream(os.File.WriteError) = undefined; -const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB +pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB const usage = \\usage: zig [command] [options] @@ -510,7 +510,7 @@ fn cmdBuildObj(allocator: *Allocator, args: []const []const u8) !void { return buildOutputType(allocator, args, Compilation.Kind.Obj); } -const usage_fmt = +pub const usage_fmt = \\usage: zig fmt [file]... \\ \\ Formats the input files and modifies them in-place. @@ -527,7 +527,7 @@ const usage_fmt = \\ ; -const args_fmt_spec = []Flag{ +pub const args_fmt_spec = []Flag{ Flag.Bool("--help"), Flag.Bool("--check"), Flag.Option("--color", []const []const u8{ diff --git a/src/main.cpp b/src/main.cpp index 73a1d75d42..78446f9e98 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,8 +21,8 @@ static int print_error_usage(const char *arg0) { return EXIT_FAILURE; } -static int print_full_usage(const char *arg0) { - fprintf(stdout, +static int print_full_usage(const char *arg0, FILE *file, int return_code) { + fprintf(file, "Usage: %s [command] [options]\n" "\n" "Commands:\n" @@ -31,6 +31,7 @@ static int print_full_usage(const char *arg0) { " build-lib [source] create library from source or object files\n" " build-obj [source] create object from source or assembly\n" " builtin show the source code of that @import(\"builtin\")\n" + " fmt parse files and render in canonical zig format\n" " help show this usage information\n" " id print the base64-encoded compiler id\n" " init-exe initialize a `zig build` application in the cwd\n" @@ -106,7 +107,7 @@ static int print_full_usage(const char *arg0) { " --test-cmd [arg] specify test execution command one arg at a time\n" " --test-cmd-bin appends test binary path to test cmd args\n" , arg0); - return EXIT_SUCCESS; + return return_code; } static const char *ZIG_ZEN = "\n" @@ -515,6 +516,31 @@ int main(int argc, char **argv) { fprintf(stderr, "\n"); } return (term.how == TerminationIdClean) ? term.code : -1; + } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) { + init_all_targets(); + Buf *fmt_runner_path = buf_alloc(); + os_path_join(get_zig_special_dir(), buf_create_from_str("fmt_runner.zig"), fmt_runner_path); + CodeGen *g = codegen_create(fmt_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(), + nullptr); + g->is_single_threaded = true; + codegen_set_out_name(g, buf_create_from_str("fmt")); + g->enable_cache = true; + + codegen_build_and_link(g); + + ZigList args = {0}; + for (int i = 2; i < argc; i += 1) { + args.append(argv[i]); + } + args.append(nullptr); + const char *exec_path = buf_ptr(&g->output_file_path); + + os_execv(exec_path, args.items); + + args.pop(); + Termination term; + os_spawn_process(exec_path, args, &term); + return term.code; } for (int i = 1; i < argc; i += 1) { @@ -527,6 +553,8 @@ int main(int argc, char **argv) { build_mode = BuildModeSafeRelease; } else if (strcmp(arg, "--release-small") == 0) { build_mode = BuildModeSmallRelease; + } else if (strcmp(arg, "--help") == 0) { + return print_full_usage(arg0, stderr, EXIT_FAILURE); } else if (strcmp(arg, "--strip") == 0) { strip = true; } else if (strcmp(arg, "--static") == 0) { @@ -1080,7 +1108,7 @@ int main(int argc, char **argv) { } } case CmdHelp: - return print_full_usage(arg0); + return print_full_usage(arg0, stdout, EXIT_SUCCESS); case CmdVersion: printf("%s\n", ZIG_VERSION_STRING); return EXIT_SUCCESS; diff --git a/std/special/fmt_runner.zig b/std/special/fmt_runner.zig new file mode 100644 index 0000000000..b6b728f5cf --- /dev/null +++ b/std/special/fmt_runner.zig @@ -0,0 +1,265 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const os = std.os; +const io = std.io; +const mem = std.mem; +const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; +const Buffer = std.Buffer; +const ast = std.zig.ast; + +const arg = @import("fmt/arg.zig"); +const self_hosted_main = @import("fmt/main.zig"); +const Args = arg.Args; +const Flag = arg.Flag; +const errmsg = @import("fmt/errmsg.zig"); + +var stderr_file: os.File = undefined; +var stderr: *io.OutStream(os.File.WriteError) = undefined; +var stdout: *io.OutStream(os.File.WriteError) = undefined; + +// This brings `zig fmt` to stage 1. +pub fn main() !void { + // Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived, + // one shot program. We don't need to waste time freeing memory and finding places to squish + // bytes into. So we free everything all at once at the very end. + var direct_allocator = std.heap.DirectAllocator.init(); + var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator); + const allocator = &arena.allocator; + + var stdout_file = try std.io.getStdOut(); + var stdout_out_stream = stdout_file.outStream(); + stdout = &stdout_out_stream.stream; + + stderr_file = try std.io.getStdErr(); + var stderr_out_stream = stderr_file.outStream(); + stderr = &stderr_out_stream.stream; + const args = try std.os.argsAlloc(allocator); + + var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args); + defer flags.deinit(); + + if (flags.present("help")) { + try stdout.write(self_hosted_main.usage_fmt); + os.exit(0); + } + + const color = blk: { + if (flags.single("color")) |color_flag| { + if (mem.eql(u8, color_flag, "auto")) { + break :blk errmsg.Color.Auto; + } else if (mem.eql(u8, color_flag, "on")) { + break :blk errmsg.Color.On; + } else if (mem.eql(u8, color_flag, "off")) { + break :blk errmsg.Color.Off; + } else unreachable; + } else { + break :blk errmsg.Color.Auto; + } + }; + + if (flags.present("stdin")) { + if (flags.positionals.len != 0) { + try stderr.write("cannot use --stdin with positional arguments\n"); + os.exit(1); + } + + var stdin_file = try io.getStdIn(); + var stdin = stdin_file.inStream(); + + const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size); + defer allocator.free(source_code); + + var tree = std.zig.parse(allocator, source_code) catch |err| { + try stderr.print("error parsing stdin: {}\n", err); + os.exit(1); + }; + defer tree.deinit(); + + var error_it = tree.errors.iterator(0); + while (error_it.next()) |parse_error| { + try printErrMsgToFile(allocator, parse_error, &tree, "", stderr_file, color); + } + if (tree.errors.len != 0) { + os.exit(1); + } + if (flags.present("check")) { + const anything_changed = try std.zig.render(allocator, io.null_out_stream, &tree); + const code = if (anything_changed) u8(1) else u8(0); + os.exit(code); + } + + _ = try std.zig.render(allocator, stdout, &tree); + return; + } + + if (flags.positionals.len == 0) { + try stderr.write("expected at least one source file argument\n"); + os.exit(1); + } + + if (flags.positionals.len == 0) { + try stderr.write("expected at least one source file argument\n"); + os.exit(1); + } + + var fmt = Fmt{ + .seen = Fmt.SeenMap.init(allocator), + .any_error = false, + .color = color, + .allocator = allocator, + }; + + const check_mode = flags.present("check"); + + for (flags.positionals.toSliceConst()) |file_path| { + try fmtPath(&fmt, file_path, check_mode); + } + if (fmt.any_error) { + os.exit(1); + } +} + +const FmtError = error{ + SystemResources, + OperationAborted, + IoPending, + BrokenPipe, + Unexpected, + WouldBlock, + FileClosed, + DestinationAddressRequired, + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + AccessDenied, + OutOfMemory, + RenameAcrossMountPoints, + ReadOnlyFileSystem, + LinkQuotaExceeded, + FileBusy, +} || os.File.OpenError; + +fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { + const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); + defer fmt.allocator.free(file_path); + + if (try fmt.seen.put(file_path, {})) |_| return; + + const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { + error.IsDir, error.AccessDenied => { + // TODO make event based (and dir.next()) + var dir = try std.os.Dir.open(fmt.allocator, file_path); + defer dir.close(); + + while (try dir.next()) |entry| { + if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { + const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); + try fmtPath(fmt, full_path, check_mode); + } + } + return; + }, + else => { + // TODO lock stderr printing + try stderr.print("unable to open '{}': {}\n", file_path, err); + fmt.any_error = true; + return; + }, + }; + defer fmt.allocator.free(source_code); + + var tree = std.zig.parse(fmt.allocator, source_code) catch |err| { + try stderr.print("error parsing file '{}': {}\n", file_path, err); + fmt.any_error = true; + return; + }; + defer tree.deinit(); + + var error_it = tree.errors.iterator(0); + while (error_it.next()) |parse_error| { + try printErrMsgToFile(fmt.allocator, parse_error, &tree, file_path, stderr_file, fmt.color); + } + if (tree.errors.len != 0) { + fmt.any_error = true; + return; + } + + if (check_mode) { + const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, &tree); + if (anything_changed) { + try stderr.print("{}\n", file_path); + fmt.any_error = true; + } + } else { + // TODO make this evented + const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); + defer baf.destroy(); + + const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), &tree); + if (anything_changed) { + try stderr.print("{}\n", file_path); + try baf.finish(); + } + } +} + +const Fmt = struct { + seen: SeenMap, + any_error: bool, + color: errmsg.Color, + allocator: *mem.Allocator, + + const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); +}; + +fn printErrMsgToFile(allocator: *mem.Allocator, parse_error: *const ast.Error, tree: *ast.Tree, + path: []const u8, file: os.File, color: errmsg.Color,) !void +{ + const color_on = switch (color) { + errmsg.Color.Auto => file.isTty(), + errmsg.Color.On => true, + errmsg.Color.Off => false, + }; + const lok_token = parse_error.loc(); + const span = errmsg.Span{ + .first = lok_token, + .last = lok_token, + }; + + const first_token = tree.tokens.at(span.first); + const last_token = tree.tokens.at(span.last); + const start_loc = tree.tokenLocationPtr(0, first_token); + const end_loc = tree.tokenLocationPtr(first_token.end, last_token); + + var text_buf = try std.Buffer.initSize(allocator, 0); + var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; + try parse_error.render(&tree.tokens, out_stream); + const text = text_buf.toOwnedSlice(); + + const stream = &file.outStream().stream; + if (!color_on) { + try stream.print( + "{}:{}:{}: error: {}\n", + path, + start_loc.line + 1, + start_loc.column + 1, + text, + ); + return; + } + + try stream.print( + "{}:{}:{}: error: {}\n{}\n", + path, + start_loc.line + 1, + start_loc.column + 1, + text, + tree.source[start_loc.line_start..start_loc.line_end], + ); + try stream.writeByteNTimes(' ', start_loc.column); + try stream.writeByteNTimes('~', last_token.end - first_token.start); + try stream.write("\n"); +}