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 ;