zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 9ebce51e164c6d35e4f412eb747fa8d3c6ea84b3 (tree)
parent 9996f8b9b17f7e04be64ac220c9bad743b45a2b7
Author: mlugg <mlugg@mlugg.co.uk>
Date:   Tue, 12 Nov 2024 22:53:24 +0000

compiler: un-jit `zig fmt`

This command being JITed leads to a substantially worse first-time user
experience, since you have to wait for upwards of 20 seconds for
`fmt.zig` to build. This is especially bad when your editor is
configured to run `zig fmt` on save and does so in a blocking manner. As
such, it makes sense from a usability perspective to not JIT this
particular command.

Diffstat:
Dlib/compiler/fmt.zig | 343-------------------------------------------------------------------------------
Asrc/fmt.zig | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.zig | 5+----
3 files changed, 337 insertions(+), 347 deletions(-)

diff --git a/lib/compiler/fmt.zig b/lib/compiler/fmt.zig @@ -1,343 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const fs = std.fs; -const process = std.process; -const Allocator = std.mem.Allocator; -const Color = std.zig.Color; - -const usage_fmt = - \\Usage: zig fmt [file]... - \\ - \\ Formats the input files and modifies them in-place. - \\ Arguments can be files or directories, which are searched - \\ recursively. - \\ - \\Options: - \\ -h, --help Print this help and exit - \\ --color [auto|off|on] Enable or disable colored error messages - \\ --stdin Format code from stdin; output to stdout - \\ --check List non-conforming files and exit with an error - \\ if the list is non-empty - \\ --ast-check Run zig ast-check on every file - \\ --exclude [file] Exclude file or directory from formatting - \\ - \\ -; - -const Fmt = struct { - seen: SeenMap, - any_error: bool, - check_ast: bool, - color: Color, - gpa: Allocator, - arena: Allocator, - out_buffer: std.ArrayList(u8), - - const SeenMap = std.AutoHashMap(fs.File.INode, void); -}; - -pub fn main() !void { - var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - const gpa = arena; - - const args = try process.argsAlloc(arena); - - var color: Color = .auto; - var stdin_flag: bool = false; - var check_flag: bool = false; - var check_ast_flag: bool = false; - var input_files = std.ArrayList([]const u8).init(gpa); - defer input_files.deinit(); - var excluded_files = std.ArrayList([]const u8).init(gpa); - defer excluded_files.deinit(); - - { - var i: usize = 1; - while (i < args.len) : (i += 1) { - const arg = args[i]; - if (mem.startsWith(u8, arg, "-")) { - if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - const stdout = std.io.getStdOut().writer(); - try stdout.writeAll(usage_fmt); - return process.cleanExit(); - } else if (mem.eql(u8, arg, "--color")) { - if (i + 1 >= args.len) { - fatal("expected [auto|on|off] after --color", .{}); - } - i += 1; - const next_arg = args[i]; - color = std.meta.stringToEnum(Color, next_arg) orelse { - fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); - }; - } else if (mem.eql(u8, arg, "--stdin")) { - stdin_flag = true; - } else if (mem.eql(u8, arg, "--check")) { - check_flag = true; - } else if (mem.eql(u8, arg, "--ast-check")) { - check_ast_flag = true; - } else if (mem.eql(u8, arg, "--exclude")) { - if (i + 1 >= args.len) { - fatal("expected parameter after --exclude", .{}); - } - i += 1; - const next_arg = args[i]; - try excluded_files.append(next_arg); - } else { - fatal("unrecognized parameter: '{s}'", .{arg}); - } - } else { - try input_files.append(arg); - } - } - } - - if (stdin_flag) { - if (input_files.items.len != 0) { - fatal("cannot use --stdin with positional arguments", .{}); - } - - const stdin = std.io.getStdIn(); - const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| { - fatal("unable to read stdin: {}", .{err}); - }; - defer gpa.free(source_code); - - var tree = std.zig.Ast.parse(gpa, source_code, .zig) catch |err| { - fatal("error parsing stdin: {}", .{err}); - }; - defer tree.deinit(gpa); - - if (check_ast_flag) { - var zir = try std.zig.AstGen.generate(gpa, tree); - - if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); - defer wip_errors.deinit(); - try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>"); - var error_bundle = try wip_errors.toOwnedBundle(""); - defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(color.renderOptions()); - process.exit(2); - } - } else if (tree.errors.len != 0) { - try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color); - process.exit(2); - } - const formatted = try tree.render(gpa); - defer gpa.free(formatted); - - if (check_flag) { - const code: u8 = @intFromBool(mem.eql(u8, formatted, source_code)); - process.exit(code); - } - - return std.io.getStdOut().writeAll(formatted); - } - - if (input_files.items.len == 0) { - fatal("expected at least one source file argument", .{}); - } - - var fmt = Fmt{ - .gpa = gpa, - .arena = arena, - .seen = Fmt.SeenMap.init(gpa), - .any_error = false, - .check_ast = check_ast_flag, - .color = color, - .out_buffer = std.ArrayList(u8).init(gpa), - }; - defer fmt.seen.deinit(); - defer fmt.out_buffer.deinit(); - - // Mark any excluded files/directories as already seen, - // so that they are skipped later during actual processing - for (excluded_files.items) |file_path| { - const stat = fs.cwd().statFile(file_path) catch |err| switch (err) { - error.FileNotFound => continue, - // On Windows, statFile does not work for directories - error.IsDir => dir: { - var dir = try fs.cwd().openDir(file_path, .{}); - defer dir.close(); - break :dir try dir.stat(); - }, - else => |e| return e, - }; - try fmt.seen.put(stat.inode, {}); - } - - for (input_files.items) |file_path| { - try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path); - } - if (fmt.any_error) { - process.exit(1); - } -} - -const FmtError = error{ - SystemResources, - OperationAborted, - IoPending, - BrokenPipe, - Unexpected, - WouldBlock, - Canceled, - FileClosed, - DestinationAddressRequired, - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - AccessDenied, - OutOfMemory, - RenameAcrossMountPoints, - ReadOnlyFileSystem, - LinkQuotaExceeded, - FileBusy, - EndOfStream, - Unseekable, - NotOpenForWriting, - UnsupportedEncoding, - ConnectionResetByPeer, - SocketNotConnected, - LockViolation, - NetNameDeleted, - InvalidArgument, - ProcessNotFound, -} || fs.File.OpenError; - -fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { - fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { - error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), - else => { - std.log.err("unable to format '{s}': {s}", .{ file_path, @errorName(err) }); - fmt.any_error = true; - return; - }, - }; -} - -fn fmtPathDir( - fmt: *Fmt, - file_path: []const u8, - check_mode: bool, - parent_dir: fs.Dir, - parent_sub_path: []const u8, -) FmtError!void { - var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); - defer dir.close(); - - const stat = try dir.stat(); - if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; - - var dir_it = dir.iterate(); - while (try dir_it.next()) |entry| { - const is_dir = entry.kind == .directory; - - if (mem.startsWith(u8, entry.name, ".")) continue; - - if (is_dir or entry.kind == .file and (mem.endsWith(u8, entry.name, ".zig") or mem.endsWith(u8, entry.name, ".zon"))) { - const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); - defer fmt.gpa.free(full_path); - - if (is_dir) { - try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); - } else { - fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { - std.log.err("unable to format '{s}': {s}", .{ full_path, @errorName(err) }); - fmt.any_error = true; - return; - }; - } - } - } -} - -fn fmtPathFile( - fmt: *Fmt, - file_path: []const u8, - check_mode: bool, - dir: fs.Dir, - sub_path: []const u8, -) FmtError!void { - const source_file = try dir.openFile(sub_path, .{}); - var file_closed = false; - errdefer if (!file_closed) source_file.close(); - - const stat = try source_file.stat(); - - if (stat.kind == .directory) - return error.IsDir; - - const gpa = fmt.gpa; - const source_code = try std.zig.readSourceFileToEndAlloc( - gpa, - source_file, - std.math.cast(usize, stat.size) orelse return error.FileTooBig, - ); - defer gpa.free(source_code); - - source_file.close(); - file_closed = true; - - // Add to set after no longer possible to get error.IsDir. - if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; - - var tree = try std.zig.Ast.parse(gpa, source_code, .zig); - defer tree.deinit(gpa); - - if (tree.errors.len != 0) { - try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color); - fmt.any_error = true; - return; - } - - if (fmt.check_ast) { - if (stat.size > std.zig.max_src_size) - return error.FileTooBig; - - var zir = try std.zig.AstGen.generate(gpa, tree); - defer zir.deinit(gpa); - - if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); - defer wip_errors.deinit(); - try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); - var error_bundle = try wip_errors.toOwnedBundle(""); - defer error_bundle.deinit(gpa); - error_bundle.renderToStdErr(fmt.color.renderOptions()); - fmt.any_error = true; - } - } - - // As a heuristic, we make enough capacity for the same as the input source. - fmt.out_buffer.shrinkRetainingCapacity(0); - try fmt.out_buffer.ensureTotalCapacity(source_code.len); - - try tree.renderToArrayList(&fmt.out_buffer, .{}); - if (mem.eql(u8, fmt.out_buffer.items, source_code)) - return; - - if (check_mode) { - const stdout = std.io.getStdOut().writer(); - try stdout.print("{s}\n", .{file_path}); - fmt.any_error = true; - } else { - var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode }); - defer af.deinit(); - - try af.file.writeAll(fmt.out_buffer.items); - try af.finish(); - const stdout = std.io.getStdOut().writer(); - try stdout.print("{s}\n", .{file_path}); - } -} - -fn fatal(comptime format: []const u8, args: anytype) noreturn { - std.log.err(format, args); - process.exit(1); -} diff --git a/src/fmt.zig b/src/fmt.zig @@ -0,0 +1,336 @@ +const usage_fmt = + \\Usage: zig fmt [file]... + \\ + \\ Formats the input files and modifies them in-place. + \\ Arguments can be files or directories, which are searched + \\ recursively. + \\ + \\Options: + \\ -h, --help Print this help and exit + \\ --color [auto|off|on] Enable or disable colored error messages + \\ --stdin Format code from stdin; output to stdout + \\ --check List non-conforming files and exit with an error + \\ if the list is non-empty + \\ --ast-check Run zig ast-check on every file + \\ --exclude [file] Exclude file or directory from formatting + \\ + \\ +; + +const Fmt = struct { + seen: SeenMap, + any_error: bool, + check_ast: bool, + color: Color, + gpa: Allocator, + arena: Allocator, + out_buffer: std.ArrayList(u8), + + const SeenMap = std.AutoHashMap(fs.File.INode, void); +}; + +pub fn run( + gpa: Allocator, + arena: Allocator, + args: []const []const u8, +) !void { + var color: Color = .auto; + var stdin_flag: bool = false; + var check_flag: bool = false; + var check_ast_flag: bool = false; + var input_files = std.ArrayList([]const u8).init(gpa); + defer input_files.deinit(); + var excluded_files = std.ArrayList([]const u8).init(gpa); + defer excluded_files.deinit(); + + { + var i: usize = 0; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (mem.startsWith(u8, arg, "-")) { + if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { + const stdout = std.io.getStdOut().writer(); + try stdout.writeAll(usage_fmt); + return process.cleanExit(); + } else if (mem.eql(u8, arg, "--color")) { + if (i + 1 >= args.len) { + fatal("expected [auto|on|off] after --color", .{}); + } + i += 1; + const next_arg = args[i]; + color = std.meta.stringToEnum(Color, next_arg) orelse { + fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); + }; + } else if (mem.eql(u8, arg, "--stdin")) { + stdin_flag = true; + } else if (mem.eql(u8, arg, "--check")) { + check_flag = true; + } else if (mem.eql(u8, arg, "--ast-check")) { + check_ast_flag = true; + } else if (mem.eql(u8, arg, "--exclude")) { + if (i + 1 >= args.len) { + fatal("expected parameter after --exclude", .{}); + } + i += 1; + const next_arg = args[i]; + try excluded_files.append(next_arg); + } else { + fatal("unrecognized parameter: '{s}'", .{arg}); + } + } else { + try input_files.append(arg); + } + } + } + + if (stdin_flag) { + if (input_files.items.len != 0) { + fatal("cannot use --stdin with positional arguments", .{}); + } + + const stdin = std.io.getStdIn(); + const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| { + fatal("unable to read stdin: {}", .{err}); + }; + defer gpa.free(source_code); + + var tree = std.zig.Ast.parse(gpa, source_code, .zig) catch |err| { + fatal("error parsing stdin: {}", .{err}); + }; + defer tree.deinit(gpa); + + if (check_ast_flag) { + var zir = try std.zig.AstGen.generate(gpa, tree); + + if (zir.hasCompileErrors()) { + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>"); + var error_bundle = try wip_errors.toOwnedBundle(""); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(color.renderOptions()); + process.exit(2); + } + } else if (tree.errors.len != 0) { + try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color); + process.exit(2); + } + const formatted = try tree.render(gpa); + defer gpa.free(formatted); + + if (check_flag) { + const code: u8 = @intFromBool(mem.eql(u8, formatted, source_code)); + process.exit(code); + } + + return std.io.getStdOut().writeAll(formatted); + } + + if (input_files.items.len == 0) { + fatal("expected at least one source file argument", .{}); + } + + var fmt = Fmt{ + .gpa = gpa, + .arena = arena, + .seen = Fmt.SeenMap.init(gpa), + .any_error = false, + .check_ast = check_ast_flag, + .color = color, + .out_buffer = std.ArrayList(u8).init(gpa), + }; + defer fmt.seen.deinit(); + defer fmt.out_buffer.deinit(); + + // Mark any excluded files/directories as already seen, + // so that they are skipped later during actual processing + for (excluded_files.items) |file_path| { + const stat = fs.cwd().statFile(file_path) catch |err| switch (err) { + error.FileNotFound => continue, + // On Windows, statFile does not work for directories + error.IsDir => dir: { + var dir = try fs.cwd().openDir(file_path, .{}); + defer dir.close(); + break :dir try dir.stat(); + }, + else => |e| return e, + }; + try fmt.seen.put(stat.inode, {}); + } + + for (input_files.items) |file_path| { + try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path); + } + if (fmt.any_error) { + process.exit(1); + } +} + +const FmtError = error{ + SystemResources, + OperationAborted, + IoPending, + BrokenPipe, + Unexpected, + WouldBlock, + Canceled, + FileClosed, + DestinationAddressRequired, + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + AccessDenied, + OutOfMemory, + RenameAcrossMountPoints, + ReadOnlyFileSystem, + LinkQuotaExceeded, + FileBusy, + EndOfStream, + Unseekable, + NotOpenForWriting, + UnsupportedEncoding, + ConnectionResetByPeer, + SocketNotConnected, + LockViolation, + NetNameDeleted, + InvalidArgument, + ProcessNotFound, +} || fs.File.OpenError; + +fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { + fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { + error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), + else => { + std.log.err("unable to format '{s}': {s}", .{ file_path, @errorName(err) }); + fmt.any_error = true; + return; + }, + }; +} + +fn fmtPathDir( + fmt: *Fmt, + file_path: []const u8, + check_mode: bool, + parent_dir: fs.Dir, + parent_sub_path: []const u8, +) FmtError!void { + var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); + defer dir.close(); + + const stat = try dir.stat(); + if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; + + var dir_it = dir.iterate(); + while (try dir_it.next()) |entry| { + const is_dir = entry.kind == .directory; + + if (mem.startsWith(u8, entry.name, ".")) continue; + + if (is_dir or entry.kind == .file and (mem.endsWith(u8, entry.name, ".zig") or mem.endsWith(u8, entry.name, ".zon"))) { + const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); + defer fmt.gpa.free(full_path); + + if (is_dir) { + try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); + } else { + fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { + std.log.err("unable to format '{s}': {s}", .{ full_path, @errorName(err) }); + fmt.any_error = true; + return; + }; + } + } + } +} + +fn fmtPathFile( + fmt: *Fmt, + file_path: []const u8, + check_mode: bool, + dir: fs.Dir, + sub_path: []const u8, +) FmtError!void { + const source_file = try dir.openFile(sub_path, .{}); + var file_closed = false; + errdefer if (!file_closed) source_file.close(); + + const stat = try source_file.stat(); + + if (stat.kind == .directory) + return error.IsDir; + + const gpa = fmt.gpa; + const source_code = try std.zig.readSourceFileToEndAlloc( + gpa, + source_file, + std.math.cast(usize, stat.size) orelse return error.FileTooBig, + ); + defer gpa.free(source_code); + + source_file.close(); + file_closed = true; + + // Add to set after no longer possible to get error.IsDir. + if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; + + var tree = try std.zig.Ast.parse(gpa, source_code, .zig); + defer tree.deinit(gpa); + + if (tree.errors.len != 0) { + try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color); + fmt.any_error = true; + return; + } + + if (fmt.check_ast) { + if (stat.size > std.zig.max_src_size) + return error.FileTooBig; + + var zir = try std.zig.AstGen.generate(gpa, tree); + defer zir.deinit(gpa); + + if (zir.hasCompileErrors()) { + var wip_errors: std.zig.ErrorBundle.Wip = undefined; + try wip_errors.init(gpa); + defer wip_errors.deinit(); + try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); + var error_bundle = try wip_errors.toOwnedBundle(""); + defer error_bundle.deinit(gpa); + error_bundle.renderToStdErr(fmt.color.renderOptions()); + fmt.any_error = true; + } + } + + // As a heuristic, we make enough capacity for the same as the input source. + fmt.out_buffer.shrinkRetainingCapacity(0); + try fmt.out_buffer.ensureTotalCapacity(source_code.len); + + try tree.renderToArrayList(&fmt.out_buffer, .{}); + if (mem.eql(u8, fmt.out_buffer.items, source_code)) + return; + + if (check_mode) { + const stdout = std.io.getStdOut().writer(); + try stdout.print("{s}\n", .{file_path}); + fmt.any_error = true; + } else { + var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode }); + defer af.deinit(); + + try af.file.writeAll(fmt.out_buffer.items); + try af.finish(); + const stdout = std.io.getStdOut().writer(); + try stdout.print("{s}\n", .{file_path}); + } +} + +const std = @import("std"); +const mem = std.mem; +const fs = std.fs; +const process = std.process; +const Allocator = std.mem.Allocator; +const Color = std.zig.Color; +const fatal = std.process.fatal; diff --git a/src/main.zig b/src/main.zig @@ -309,10 +309,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { .server = use_server, }); } else if (mem.eql(u8, cmd, "fmt")) { - return jitCmd(gpa, arena, cmd_args, .{ - .cmd_name = "fmt", - .root_src_path = "fmt.zig", - }); + return @import("fmt.zig").run(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "objcopy")) { return jitCmd(gpa, arena, cmd_args, .{ .cmd_name = "objcopy",