zig

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

blob ee4cf9bb (31835B) - Raw


      1 const std = @import("std");
      2 const io = std.io;
      3 const fs = std.fs;
      4 const mem = std.mem;
      5 const process = std.process;
      6 const Allocator = mem.Allocator;
      7 const ArrayList = std.ArrayList;
      8 const ast = std.zig.ast;
      9 const Module = @import("Module.zig");
     10 const link = @import("link.zig");
     11 const Package = @import("Package.zig");
     12 const zir = @import("zir.zig");
     13 
     14 // TODO Improve async I/O enough that we feel comfortable doing this.
     15 //pub const io_mode = .evented;
     16 
     17 pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
     18 
     19 pub const Color = enum {
     20     Auto,
     21     Off,
     22     On,
     23 };
     24 
     25 const usage =
     26     \\Usage: zig [command] [options]
     27     \\
     28     \\Commands:
     29     \\
     30     \\  build-exe  [source]      Create executable from source or object files
     31     \\  build-lib  [source]      Create library from source or object files
     32     \\  build-obj  [source]      Create object from source or assembly
     33     \\  fmt        [source]      Parse file and render in canonical zig format
     34     \\  targets                  List available compilation targets
     35     \\  version                  Print version number and exit
     36     \\  zen                      Print zen of zig and exit
     37     \\
     38     \\
     39 ;
     40 
     41 pub fn main() !void {
     42     // TODO general purpose allocator in the zig std lib
     43     const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator;
     44     var arena_instance = std.heap.ArenaAllocator.init(gpa);
     45     defer arena_instance.deinit();
     46     const arena = &arena_instance.allocator;
     47 
     48     const args = try process.argsAlloc(arena);
     49 
     50     if (args.len <= 1) {
     51         std.debug.warn("expected command argument\n\n{}", .{usage});
     52         process.exit(1);
     53     }
     54 
     55     const cmd = args[1];
     56     const cmd_args = args[2..];
     57     if (mem.eql(u8, cmd, "build-exe")) {
     58         return buildOutputType(gpa, arena, cmd_args, .Exe);
     59     } else if (mem.eql(u8, cmd, "build-lib")) {
     60         return buildOutputType(gpa, arena, cmd_args, .Lib);
     61     } else if (mem.eql(u8, cmd, "build-obj")) {
     62         return buildOutputType(gpa, arena, cmd_args, .Obj);
     63     } else if (mem.eql(u8, cmd, "fmt")) {
     64         return cmdFmt(gpa, cmd_args);
     65     } else if (mem.eql(u8, cmd, "targets")) {
     66         const info = try std.zig.system.NativeTargetInfo.detect(arena, .{});
     67         const stdout = io.getStdOut().outStream();
     68         return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
     69     } else if (mem.eql(u8, cmd, "version")) {
     70         // Need to set up the build script to give the version as a comptime value.
     71         std.debug.warn("TODO version command not implemented yet\n", .{});
     72         return error.Unimplemented;
     73     } else if (mem.eql(u8, cmd, "zen")) {
     74         try io.getStdOut().writeAll(info_zen);
     75     } else if (mem.eql(u8, cmd, "help")) {
     76         try io.getStdOut().writeAll(usage);
     77     } else {
     78         std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage });
     79         process.exit(1);
     80     }
     81 }
     82 
     83 const usage_build_generic =
     84     \\Usage: zig build-exe <options> [files]
     85     \\       zig build-lib <options> [files]
     86     \\       zig build-obj <options> [files]
     87     \\
     88     \\Supported file types:
     89     \\     (planned)      .zig    Zig source code
     90     \\                    .zir    Zig Intermediate Representation code
     91     \\     (planned)        .o    ELF object file
     92     \\     (planned)        .o    MACH-O (macOS) object file
     93     \\     (planned)      .obj    COFF (Windows) object file
     94     \\     (planned)      .lib    COFF (Windows) static library
     95     \\     (planned)        .a    ELF static library
     96     \\     (planned)       .so    ELF shared object (dynamic link)
     97     \\     (planned)      .dll    Windows Dynamic Link Library
     98     \\     (planned)    .dylib    MACH-O (macOS) dynamic library
     99     \\     (planned)        .s    Target-specific assembly source code
    100     \\     (planned)        .S    Assembly with C preprocessor (requires LLVM extensions)
    101     \\     (planned)        .c    C source code (requires LLVM extensions)
    102     \\     (planned)      .cpp    C++ source code (requires LLVM extensions)
    103     \\                            Other C++ extensions: .C .cc .cxx
    104     \\
    105     \\General Options:
    106     \\  -h, --help                Print this help and exit
    107     \\  --watch                   Enable compiler REPL
    108     \\  --color [auto|off|on]     Enable or disable colored error messages
    109     \\  -femit-bin[=path]         (default) output machine code
    110     \\  -fno-emit-bin             Do not output machine code
    111     \\
    112     \\Compile Options:
    113     \\  -target [name]            <arch><sub>-<os>-<abi> see the targets command
    114     \\  -mcpu [cpu]               Specify target CPU and feature set
    115     \\  --name [name]             Override output name
    116     \\  --mode [mode]             Set the build mode
    117     \\    Debug                   (default) optimizations off, safety on
    118     \\    ReleaseFast             optimizations on, safety off
    119     \\    ReleaseSafe             optimizations on, safety on
    120     \\    ReleaseSmall            optimize for small binary, safety off
    121     \\  --dynamic                 Force output to be dynamically linked
    122     \\  --strip                   Exclude debug symbols
    123     \\
    124     \\Link Options:
    125     \\  -l[lib], --library [lib]  Link against system library
    126     \\  --dynamic-linker [path]   Set the dynamic interpreter path (usually ld.so)
    127     \\  --version [ver]           Dynamic library semver
    128     \\
    129     \\Debug Options (Zig Compiler Development):
    130     \\  -ftime-report             Print timing diagnostics
    131     \\  --debug-tokenize          verbose tokenization
    132     \\  --debug-ast-tree          verbose parsing into an AST (tree view)
    133     \\  --debug-ast-fmt           verbose parsing into an AST (render source)
    134     \\  --debug-ir                verbose Zig IR
    135     \\  --debug-link              verbose linking
    136     \\  --debug-codegen           verbose machine code generation
    137     \\
    138 ;
    139 
    140 const Emit = union(enum) {
    141     no,
    142     yes_default_path,
    143     yes: []const u8,
    144 };
    145 
    146 fn buildOutputType(
    147     gpa: *Allocator,
    148     arena: *Allocator,
    149     args: []const []const u8,
    150     output_mode: std.builtin.OutputMode,
    151 ) !void {
    152     var color: Color = .Auto;
    153     var build_mode: std.builtin.Mode = .Debug;
    154     var provided_name: ?[]const u8 = null;
    155     var link_mode: ?std.builtin.LinkMode = null;
    156     var root_src_file: ?[]const u8 = null;
    157     var version: std.builtin.Version = .{ .major = 0, .minor = 0, .patch = 0 };
    158     var strip = false;
    159     var watch = false;
    160     var debug_tokenize = false;
    161     var debug_ast_tree = false;
    162     var debug_ast_fmt = false;
    163     var debug_link = false;
    164     var debug_ir = false;
    165     var debug_codegen = false;
    166     var time_report = false;
    167     var emit_bin: Emit = .yes_default_path;
    168     var emit_zir: Emit = .no;
    169     var target_arch_os_abi: []const u8 = "native";
    170     var target_mcpu: ?[]const u8 = null;
    171     var target_dynamic_linker: ?[]const u8 = null;
    172 
    173     var system_libs = std.ArrayList([]const u8).init(gpa);
    174     defer system_libs.deinit();
    175 
    176     {
    177         var i: usize = 0;
    178         while (i < args.len) : (i += 1) {
    179             const arg = args[i];
    180             if (mem.startsWith(u8, arg, "-")) {
    181                 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
    182                     try io.getStdOut().writeAll(usage_build_generic);
    183                     process.exit(0);
    184                 } else if (mem.eql(u8, arg, "--color")) {
    185                     if (i + 1 >= args.len) {
    186                         std.debug.warn("expected [auto|on|off] after --color\n", .{});
    187                         process.exit(1);
    188                     }
    189                     i += 1;
    190                     const next_arg = args[i];
    191                     if (mem.eql(u8, next_arg, "auto")) {
    192                         color = .Auto;
    193                     } else if (mem.eql(u8, next_arg, "on")) {
    194                         color = .On;
    195                     } else if (mem.eql(u8, next_arg, "off")) {
    196                         color = .Off;
    197                     } else {
    198                         std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
    199                         process.exit(1);
    200                     }
    201                 } else if (mem.eql(u8, arg, "--mode")) {
    202                     if (i + 1 >= args.len) {
    203                         std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
    204                         process.exit(1);
    205                     }
    206                     i += 1;
    207                     const next_arg = args[i];
    208                     if (mem.eql(u8, next_arg, "Debug")) {
    209                         build_mode = .Debug;
    210                     } else if (mem.eql(u8, next_arg, "ReleaseSafe")) {
    211                         build_mode = .ReleaseSafe;
    212                     } else if (mem.eql(u8, next_arg, "ReleaseFast")) {
    213                         build_mode = .ReleaseFast;
    214                     } else if (mem.eql(u8, next_arg, "ReleaseSmall")) {
    215                         build_mode = .ReleaseSmall;
    216                     } else {
    217                         std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
    218                         process.exit(1);
    219                     }
    220                 } else if (mem.eql(u8, arg, "--name")) {
    221                     if (i + 1 >= args.len) {
    222                         std.debug.warn("expected parameter after --name\n", .{});
    223                         process.exit(1);
    224                     }
    225                     i += 1;
    226                     provided_name = args[i];
    227                 } else if (mem.eql(u8, arg, "--library")) {
    228                     if (i + 1 >= args.len) {
    229                         std.debug.warn("expected parameter after --library\n", .{});
    230                         process.exit(1);
    231                     }
    232                     i += 1;
    233                     try system_libs.append(args[i]);
    234                 } else if (mem.eql(u8, arg, "--version")) {
    235                     if (i + 1 >= args.len) {
    236                         std.debug.warn("expected parameter after --version\n", .{});
    237                         process.exit(1);
    238                     }
    239                     i += 1;
    240                     version = std.builtin.Version.parse(args[i]) catch |err| {
    241                         std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
    242                         process.exit(1);
    243                     };
    244                 } else if (mem.eql(u8, arg, "-target")) {
    245                     if (i + 1 >= args.len) {
    246                         std.debug.warn("expected parameter after -target\n", .{});
    247                         process.exit(1);
    248                     }
    249                     i += 1;
    250                     target_arch_os_abi = args[i];
    251                 } else if (mem.eql(u8, arg, "-mcpu")) {
    252                     if (i + 1 >= args.len) {
    253                         std.debug.warn("expected parameter after -mcpu\n", .{});
    254                         process.exit(1);
    255                     }
    256                     i += 1;
    257                     target_mcpu = args[i];
    258                 } else if (mem.startsWith(u8, arg, "-mcpu=")) {
    259                     target_mcpu = arg["-mcpu=".len..];
    260                 } else if (mem.eql(u8, arg, "--dynamic-linker")) {
    261                     if (i + 1 >= args.len) {
    262                         std.debug.warn("expected parameter after --dynamic-linker\n", .{});
    263                         process.exit(1);
    264                     }
    265                     i += 1;
    266                     target_dynamic_linker = args[i];
    267                 } else if (mem.eql(u8, arg, "--watch")) {
    268                     watch = true;
    269                 } else if (mem.eql(u8, arg, "-ftime-report")) {
    270                     time_report = true;
    271                 } else if (mem.eql(u8, arg, "-femit-bin")) {
    272                     emit_bin = .yes_default_path;
    273                 } else if (mem.startsWith(u8, arg, "-femit-bin=")) {
    274                     emit_bin = .{ .yes = arg["-femit-bin=".len..] };
    275                 } else if (mem.eql(u8, arg, "-fno-emit-bin")) {
    276                     emit_bin = .no;
    277                 } else if (mem.eql(u8, arg, "-femit-zir")) {
    278                     emit_zir = .yes_default_path;
    279                 } else if (mem.startsWith(u8, arg, "-femit-zir=")) {
    280                     emit_zir = .{ .yes = arg["-femit-zir=".len..] };
    281                 } else if (mem.eql(u8, arg, "-fno-emit-zir")) {
    282                     emit_zir = .no;
    283                 } else if (mem.eql(u8, arg, "-dynamic")) {
    284                     link_mode = .Dynamic;
    285                 } else if (mem.eql(u8, arg, "-static")) {
    286                     link_mode = .Static;
    287                 } else if (mem.eql(u8, arg, "--strip")) {
    288                     strip = true;
    289                 } else if (mem.eql(u8, arg, "--debug-tokenize")) {
    290                     debug_tokenize = true;
    291                 } else if (mem.eql(u8, arg, "--debug-ast-tree")) {
    292                     debug_ast_tree = true;
    293                 } else if (mem.eql(u8, arg, "--debug-ast-fmt")) {
    294                     debug_ast_fmt = true;
    295                 } else if (mem.eql(u8, arg, "--debug-link")) {
    296                     debug_link = true;
    297                 } else if (mem.eql(u8, arg, "--debug-ir")) {
    298                     debug_ir = true;
    299                 } else if (mem.eql(u8, arg, "--debug-codegen")) {
    300                     debug_codegen = true;
    301                 } else if (mem.startsWith(u8, arg, "-l")) {
    302                     try system_libs.append(arg[2..]);
    303                 } else {
    304                     std.debug.warn("unrecognized parameter: '{}'", .{arg});
    305                     process.exit(1);
    306                 }
    307             } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {
    308                 std.debug.warn("assembly files not supported yet", .{});
    309                 process.exit(1);
    310             } else if (mem.endsWith(u8, arg, ".o") or
    311                 mem.endsWith(u8, arg, ".obj") or
    312                 mem.endsWith(u8, arg, ".a") or
    313                 mem.endsWith(u8, arg, ".lib"))
    314             {
    315                 std.debug.warn("object files and static libraries not supported yet", .{});
    316                 process.exit(1);
    317             } else if (mem.endsWith(u8, arg, ".c") or
    318                 mem.endsWith(u8, arg, ".cpp"))
    319             {
    320                 std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});
    321                 process.exit(1);
    322             } else if (mem.endsWith(u8, arg, ".so") or
    323                 mem.endsWith(u8, arg, ".dylib") or
    324                 mem.endsWith(u8, arg, ".dll"))
    325             {
    326                 std.debug.warn("linking against dynamic libraries not yet supported", .{});
    327                 process.exit(1);
    328             } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {
    329                 if (root_src_file) |other| {
    330                     std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other });
    331                     process.exit(1);
    332                 } else {
    333                     root_src_file = arg;
    334                 }
    335             } else {
    336                 std.debug.warn("unrecognized file extension of parameter '{}'", .{arg});
    337             }
    338         }
    339     }
    340 
    341     const root_name = if (provided_name) |n| n else blk: {
    342         if (root_src_file) |file| {
    343             const basename = fs.path.basename(file);
    344             var it = mem.split(basename, ".");
    345             break :blk it.next() orelse basename;
    346         } else {
    347             std.debug.warn("--name [name] not provided and unable to infer\n", .{});
    348             process.exit(1);
    349         }
    350     };
    351 
    352     if (system_libs.items.len != 0) {
    353         std.debug.warn("linking against system libraries not yet supported", .{});
    354         process.exit(1);
    355     }
    356 
    357     var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
    358     const cross_target = std.zig.CrossTarget.parse(.{
    359         .arch_os_abi = target_arch_os_abi,
    360         .cpu_features = target_mcpu,
    361         .dynamic_linker = target_dynamic_linker,
    362         .diagnostics = &diags,
    363     }) catch |err| switch (err) {
    364         error.UnknownCpuModel => {
    365             std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
    366                 diags.cpu_name.?,
    367                 @tagName(diags.arch.?),
    368             });
    369             for (diags.arch.?.allCpuModels()) |cpu| {
    370                 std.debug.warn(" {}\n", .{cpu.name});
    371             }
    372             process.exit(1);
    373         },
    374         error.UnknownCpuFeature => {
    375             std.debug.warn(
    376                 \\Unknown CPU feature: '{}'
    377                 \\Available CPU features for architecture '{}':
    378                 \\
    379             , .{
    380                 diags.unknown_feature_name,
    381                 @tagName(diags.arch.?),
    382             });
    383             for (diags.arch.?.allFeaturesList()) |feature| {
    384                 std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
    385             }
    386             process.exit(1);
    387         },
    388         else => |e| return e,
    389     };
    390 
    391     const object_format: ?std.builtin.ObjectFormat = null;
    392     var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target);
    393     if (target_info.cpu_detection_unimplemented) {
    394         // TODO We want to just use detected_info.target but implementing
    395         // CPU model & feature detection is todo so here we rely on LLVM.
    396         std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
    397         process.exit(1);
    398     }
    399 
    400     const src_path = root_src_file orelse {
    401         std.debug.warn("expected at least one file argument", .{});
    402         process.exit(1);
    403     };
    404 
    405     const bin_path = switch (emit_bin) {
    406         .no => {
    407             std.debug.warn("-fno-emit-bin not supported yet", .{});
    408             process.exit(1);
    409         },
    410         .yes_default_path => switch (output_mode) {
    411             .Exe => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.exeFileExt() }),
    412             .Lib => blk: {
    413                 const suffix = switch (link_mode orelse .Static) {
    414                     .Static => target_info.target.staticLibSuffix(),
    415                     .Dynamic => target_info.target.dynamicLibSuffix(),
    416                 };
    417                 break :blk try std.fmt.allocPrint(arena, "{}{}{}", .{
    418                     target_info.target.libPrefix(),
    419                     root_name,
    420                     suffix,
    421                 });
    422             },
    423             .Obj => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.oFileExt() }),
    424         },
    425         .yes => |p| p,
    426     };
    427 
    428     const zir_out_path: ?[]const u8 = switch (emit_zir) {
    429         .no => null,
    430         .yes_default_path => blk: {
    431             if (root_src_file) |rsf| {
    432                 if (mem.endsWith(u8, rsf, ".zir")) {
    433                     break :blk try std.fmt.allocPrint(arena, "{}.out.zir", .{root_name});
    434                 }
    435             }
    436             break :blk try std.fmt.allocPrint(arena, "{}.zir", .{root_name});
    437         },
    438         .yes => |p| p,
    439     };
    440 
    441     const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path);
    442     defer root_pkg.destroy();
    443 
    444     var module = try Module.init(gpa, .{
    445         .target = target_info.target,
    446         .output_mode = output_mode,
    447         .root_pkg = root_pkg,
    448         .bin_file_dir = fs.cwd(),
    449         .bin_file_path = bin_path,
    450         .link_mode = link_mode,
    451         .object_format = object_format,
    452         .optimize_mode = build_mode,
    453     });
    454     defer module.deinit();
    455 
    456     const stdin = std.io.getStdIn().inStream();
    457     const stderr = std.io.getStdErr().outStream();
    458     var repl_buf: [1024]u8 = undefined;
    459 
    460     try updateModule(gpa, &module, zir_out_path);
    461 
    462     while (watch) {
    463         try stderr.print("🦎 ", .{});
    464         if (output_mode == .Exe) {
    465             try module.makeBinFileExecutable();
    466         }
    467         if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| {
    468             try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)});
    469             continue;
    470         }) |line| {
    471             if (mem.eql(u8, line, "update")) {
    472                 if (output_mode == .Exe) {
    473                     try module.makeBinFileWritable();
    474                 }
    475                 try updateModule(gpa, &module, zir_out_path);
    476             } else if (mem.eql(u8, line, "exit")) {
    477                 break;
    478             } else if (mem.eql(u8, line, "help")) {
    479                 try stderr.writeAll(repl_help);
    480             } else {
    481                 try stderr.print("unknown command: {}\n", .{line});
    482             }
    483         } else {
    484             break;
    485         }
    486     }
    487 }
    488 
    489 fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void {
    490     try module.update();
    491 
    492     var errors = try module.getAllErrorsAlloc();
    493     defer errors.deinit(module.allocator);
    494 
    495     if (errors.list.len != 0) {
    496         for (errors.list) |full_err_msg| {
    497             std.debug.warn("{}:{}:{}: error: {}\n", .{
    498                 full_err_msg.src_path,
    499                 full_err_msg.line + 1,
    500                 full_err_msg.column + 1,
    501                 full_err_msg.msg,
    502             });
    503         }
    504     }
    505 
    506     if (zir_out_path) |zop| {
    507         var new_zir_module = try zir.emit(gpa, module.*);
    508         defer new_zir_module.deinit(gpa);
    509 
    510         const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{});
    511         defer baf.destroy();
    512 
    513         try new_zir_module.writeToStream(gpa, baf.stream());
    514 
    515         try baf.finish();
    516     }
    517 }
    518 
    519 const repl_help =
    520     \\Commands:
    521     \\  update   Detect changes to source files and update output files.
    522     \\    help   Print this text
    523     \\    exit   Quit this repl
    524     \\
    525 ;
    526 
    527 pub const usage_fmt =
    528     \\usage: zig fmt [file]...
    529     \\
    530     \\   Formats the input files and modifies them in-place.
    531     \\   Arguments can be files or directories, which are searched
    532     \\   recursively.
    533     \\
    534     \\Options:
    535     \\   --help                 Print this help and exit
    536     \\   --color [auto|off|on]  Enable or disable colored error messages
    537     \\   --stdin                Format code from stdin; output to stdout
    538     \\   --check                List non-conforming files and exit with an error
    539     \\                          if the list is non-empty
    540     \\
    541     \\
    542 ;
    543 
    544 const Fmt = struct {
    545     seen: SeenMap,
    546     any_error: bool,
    547     color: Color,
    548     gpa: *Allocator,
    549 
    550     const SeenMap = std.BufSet;
    551 };
    552 
    553 pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
    554     const stderr_file = io.getStdErr();
    555     var color: Color = .Auto;
    556     var stdin_flag: bool = false;
    557     var check_flag: bool = false;
    558     var input_files = ArrayList([]const u8).init(gpa);
    559 
    560     {
    561         var i: usize = 0;
    562         while (i < args.len) : (i += 1) {
    563             const arg = args[i];
    564             if (mem.startsWith(u8, arg, "-")) {
    565                 if (mem.eql(u8, arg, "--help")) {
    566                     const stdout = io.getStdOut().outStream();
    567                     try stdout.writeAll(usage_fmt);
    568                     process.exit(0);
    569                 } else if (mem.eql(u8, arg, "--color")) {
    570                     if (i + 1 >= args.len) {
    571                         std.debug.warn("expected [auto|on|off] after --color\n", .{});
    572                         process.exit(1);
    573                     }
    574                     i += 1;
    575                     const next_arg = args[i];
    576                     if (mem.eql(u8, next_arg, "auto")) {
    577                         color = .Auto;
    578                     } else if (mem.eql(u8, next_arg, "on")) {
    579                         color = .On;
    580                     } else if (mem.eql(u8, next_arg, "off")) {
    581                         color = .Off;
    582                     } else {
    583                         std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
    584                         process.exit(1);
    585                     }
    586                 } else if (mem.eql(u8, arg, "--stdin")) {
    587                     stdin_flag = true;
    588                 } else if (mem.eql(u8, arg, "--check")) {
    589                     check_flag = true;
    590                 } else {
    591                     std.debug.warn("unrecognized parameter: '{}'", .{arg});
    592                     process.exit(1);
    593                 }
    594             } else {
    595                 try input_files.append(arg);
    596             }
    597         }
    598     }
    599 
    600     if (stdin_flag) {
    601         if (input_files.items.len != 0) {
    602             std.debug.warn("cannot use --stdin with positional arguments\n", .{});
    603             process.exit(1);
    604         }
    605 
    606         const stdin = io.getStdIn().inStream();
    607 
    608         const source_code = try stdin.readAllAlloc(gpa, max_src_size);
    609         defer gpa.free(source_code);
    610 
    611         const tree = std.zig.parse(gpa, source_code) catch |err| {
    612             std.debug.warn("error parsing stdin: {}\n", .{err});
    613             process.exit(1);
    614         };
    615         defer tree.deinit();
    616 
    617         for (tree.errors) |parse_error| {
    618             try printErrMsgToFile(gpa, parse_error, tree, "<stdin>", stderr_file, color);
    619         }
    620         if (tree.errors.len != 0) {
    621             process.exit(1);
    622         }
    623         if (check_flag) {
    624             const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree);
    625             const code = if (anything_changed) @as(u8, 1) else @as(u8, 0);
    626             process.exit(code);
    627         }
    628 
    629         const stdout = io.getStdOut().outStream();
    630         _ = try std.zig.render(gpa, stdout, tree);
    631         return;
    632     }
    633 
    634     if (input_files.items.len == 0) {
    635         std.debug.warn("expected at least one source file argument\n", .{});
    636         process.exit(1);
    637     }
    638 
    639     var fmt = Fmt{
    640         .gpa = gpa,
    641         .seen = Fmt.SeenMap.init(gpa),
    642         .any_error = false,
    643         .color = color,
    644     };
    645 
    646     for (input_files.span()) |file_path| {
    647         try fmtPath(&fmt, file_path, check_flag);
    648     }
    649     if (fmt.any_error) {
    650         process.exit(1);
    651     }
    652 }
    653 
    654 const FmtError = error{
    655     SystemResources,
    656     OperationAborted,
    657     IoPending,
    658     BrokenPipe,
    659     Unexpected,
    660     WouldBlock,
    661     FileClosed,
    662     DestinationAddressRequired,
    663     DiskQuota,
    664     FileTooBig,
    665     InputOutput,
    666     NoSpaceLeft,
    667     AccessDenied,
    668     OutOfMemory,
    669     RenameAcrossMountPoints,
    670     ReadOnlyFileSystem,
    671     LinkQuotaExceeded,
    672     FileBusy,
    673     EndOfStream,
    674 } || fs.File.OpenError;
    675 
    676 fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void {
    677     // get the real path here to avoid Windows failing on relative file paths with . or .. in them
    678     const real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| {
    679         std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
    680         fmt.any_error = true;
    681         return;
    682     };
    683     defer fmt.gpa.free(real_path);
    684 
    685     if (fmt.seen.exists(real_path)) return;
    686     try fmt.seen.put(real_path);
    687 
    688     fmtPathFile(fmt, file_path, check_mode, real_path) catch |err| switch (err) {
    689         error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, real_path),
    690         else => {
    691             std.debug.warn("unable to format '{}': {}\n", .{ file_path, err });
    692             fmt.any_error = true;
    693             return;
    694         },
    695     };
    696 }
    697 
    698 fn fmtPathDir(fmt: *Fmt, file_path: []const u8, check_mode: bool, parent_real_path: []const u8) FmtError!void {
    699     var dir = try fs.cwd().openDir(parent_real_path, .{ .iterate = true });
    700     defer dir.close();
    701 
    702     var dir_it = dir.iterate();
    703     while (try dir_it.next()) |entry| {
    704         const is_dir = entry.kind == .Directory;
    705         if (is_dir or mem.endsWith(u8, entry.name, ".zig")) {
    706             const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
    707             const sub_real_path = fs.realpathAlloc(fmt.gpa, full_path) catch |err| {
    708                 std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
    709                 fmt.any_error = true;
    710                 return;
    711             };
    712             defer fmt.gpa.free(sub_real_path);
    713 
    714             if (fmt.seen.exists(sub_real_path)) return;
    715             try fmt.seen.put(sub_real_path);
    716 
    717             if (is_dir) {
    718                 try fmtPathDir(fmt, full_path, check_mode, sub_real_path);
    719             } else {
    720                 fmtPathFile(fmt, full_path, check_mode, sub_real_path) catch |err| {
    721                     std.debug.warn("unable to format '{}': {}\n", .{ full_path, err });
    722                     fmt.any_error = true;
    723                     return;
    724                 };
    725             }
    726         }
    727     }
    728 }
    729 
    730 fn fmtPathFile(fmt: *Fmt, file_path: []const u8, check_mode: bool, real_path: []const u8) FmtError!void {
    731     const source_file = try fs.cwd().openFile(real_path, .{});
    732     defer source_file.close();
    733 
    734     const stat = try source_file.stat();
    735 
    736     if (stat.kind == .Directory)
    737         return error.IsDir;
    738 
    739     const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| switch (err) {
    740         error.ConnectionResetByPeer => unreachable,
    741         error.ConnectionTimedOut => unreachable,
    742         else => |e| return e,
    743     };
    744     defer fmt.gpa.free(source_code);
    745 
    746     const tree = try std.zig.parse(fmt.gpa, source_code);
    747     defer tree.deinit();
    748 
    749     for (tree.errors) |parse_error| {
    750         try printErrMsgToFile(fmt.gpa, parse_error, tree, file_path, std.io.getStdErr(), fmt.color);
    751     }
    752     if (tree.errors.len != 0) {
    753         fmt.any_error = true;
    754         return;
    755     }
    756 
    757     if (check_mode) {
    758         const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
    759         if (anything_changed) {
    760             std.debug.warn("{}\n", .{file_path});
    761             fmt.any_error = true;
    762         }
    763     } else {
    764         const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{ .mode = stat.mode });
    765         defer baf.destroy();
    766 
    767         const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree);
    768         if (anything_changed) {
    769             std.debug.warn("{}\n", .{file_path});
    770             try baf.finish();
    771         }
    772     }
    773 }
    774 
    775 fn printErrMsgToFile(
    776     gpa: *mem.Allocator,
    777     parse_error: ast.Error,
    778     tree: *ast.Tree,
    779     path: []const u8,
    780     file: fs.File,
    781     color: Color,
    782 ) !void {
    783     const color_on = switch (color) {
    784         .Auto => file.isTty(),
    785         .On => true,
    786         .Off => false,
    787     };
    788     const lok_token = parse_error.loc();
    789     const span_first = lok_token;
    790     const span_last = lok_token;
    791 
    792     const first_token = tree.token_locs[span_first];
    793     const last_token = tree.token_locs[span_last];
    794     const start_loc = tree.tokenLocationLoc(0, first_token);
    795     const end_loc = tree.tokenLocationLoc(first_token.end, last_token);
    796 
    797     var text_buf = std.ArrayList(u8).init(gpa);
    798     defer text_buf.deinit();
    799     const out_stream = text_buf.outStream();
    800     try parse_error.render(tree.token_ids, out_stream);
    801     const text = text_buf.span();
    802 
    803     const stream = file.outStream();
    804     try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text });
    805 
    806     if (!color_on) return;
    807 
    808     // Print \r and \t as one space each so that column counts line up
    809     for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| {
    810         try stream.writeByte(switch (byte) {
    811             '\r', '\t' => ' ',
    812             else => byte,
    813         });
    814     }
    815     try stream.writeByte('\n');
    816     try stream.writeByteNTimes(' ', start_loc.column);
    817     try stream.writeByteNTimes('~', last_token.end - first_token.start);
    818     try stream.writeByte('\n');
    819 }
    820 
    821 pub const info_zen =
    822     \\
    823     \\ * Communicate intent precisely.
    824     \\ * Edge cases matter.
    825     \\ * Favor reading code over writing code.
    826     \\ * Only one obvious way to do things.
    827     \\ * Runtime crashes are better than bugs.
    828     \\ * Compile errors are better than runtime crashes.
    829     \\ * Incremental improvements.
    830     \\ * Avoid local maximums.
    831     \\ * Reduce the amount one must remember.
    832     \\ * Minimize energy spent on coding style.
    833     \\ * Resource deallocation must succeed.
    834     \\ * Together we serve end users.
    835     \\
    836     \\
    837 ;