Commit b750e074c6

Andrew Kelley <andrew@ziglang.org>
2021-05-23 02:20:30
stage2: rework astgen command into `zig ast-check`
This addresses the use case of quickly reporting AstGen compile errors for a file, for use with an IDE for example. * Rename from `zig asgen` to `zig ast-check` * It is now a command always available; not only in debug builds. * Give it usage text and proper CLI parsing. * Support reading from stdin when no positional arg is provided. * `-t` flag makes it print textual ZIR. Without this flag, it only provides compile errors. * Support `--color` parameter to override the tty detection closes #8871
1 parent 51701fb
Changed files (1)
src/main.zig
@@ -50,6 +50,7 @@ const normal_usage =
     \\  c++              Use Zig as a drop-in C++ compiler
     \\  env              Print lib path, std path, cache directory, and version
     \\  fmt              Reformat Zig source into canonical form
+    \\  ast-check        Look for simple compile errors in any set of files
     \\  help             Print this help and exit
     \\  init-exe         Initialize a `zig build` application in the cwd
     \\  init-lib         Initialize a `zig build` library in the cwd
@@ -71,7 +72,6 @@ const debug_usage = normal_usage ++
     \\
     \\Debug Commands:
     \\
-    \\  astgen           Print ZIR code for a .zig source file
     \\  changelist       Compute mappings from old ZIR to new ZIR
     \\
 ;
@@ -239,8 +239,8 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
         return io.getStdOut().writeAll(info_zen);
     } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) {
         return io.getStdOut().writeAll(usage);
-    } else if (debug_extensions_enabled and mem.eql(u8, cmd, "astgen")) {
-        return cmdAstgen(gpa, arena, cmd_args);
+    } else if (mem.eql(u8, cmd, "ast-check")) {
+        return cmdAstCheck(gpa, arena, cmd_args);
     } else if (debug_extensions_enabled and mem.eql(u8, cmd, "changelist")) {
         return cmdChangelist(gpa, arena, cmd_args);
     } else {
@@ -2246,7 +2246,8 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
 
     if (errors.list.len != 0) {
         const ttyconf: std.debug.TTY.Config = switch (comp.color) {
-            .auto, .on => std.debug.detectTTYConfig(),
+            .auto => std.debug.detectTTYConfig(),
+            .on => .escape_codes,
             .off => .no_color,
         };
         for (errors.list) |full_err_msg| {
@@ -2823,13 +2824,17 @@ fn argvCmd(allocator: *Allocator, argv: []const []const u8) ![]u8 {
     return cmd.toOwnedSlice();
 }
 
-fn readSourceFileToEndAlloc(allocator: *mem.Allocator, input: *const fs.File, size_hint: ?usize) ![]const u8 {
+fn readSourceFileToEndAlloc(
+    allocator: *mem.Allocator,
+    input: *const fs.File,
+    size_hint: ?usize,
+) ![:0]u8 {
     const source_code = input.readToEndAllocOptions(
         allocator,
         max_src_size,
         size_hint,
         @alignOf(u16),
-        null,
+        0,
     ) catch |err| switch (err) {
         error.ConnectionResetByPeer => unreachable,
         error.ConnectionTimedOut => unreachable,
@@ -2853,7 +2858,7 @@ fn readSourceFileToEndAlloc(allocator: *mem.Allocator, input: *const fs.File, si
     // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
     if (mem.startsWith(u8, source_code, "\xff\xfe")) {
         const source_code_utf16_le = mem.bytesAsSlice(u16, source_code);
-        const source_code_utf8 = std.unicode.utf16leToUtf8Alloc(allocator, source_code_utf16_le) catch |err| switch (err) {
+        const source_code_utf8 = std.unicode.utf16leToUtf8AllocZ(allocator, source_code_utf16_le) catch |err| switch (err) {
             error.DanglingSurrogateHalf => error.UnsupportedEncoding,
             error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
             error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
@@ -3562,8 +3567,22 @@ pub fn cleanExit() void {
     }
 }
 
-/// This is only enabled for debug builds.
-pub fn cmdAstgen(
+const usage_ast_check =
+    \\Usage: zig ast-check [file]
+    \\
+    \\    Given a .zig source file, reports any compile errors that can be
+    \\    ascertained on the basis of the source code alone, without target
+    \\    information or type checking.
+    \\
+    \\    If [file] is omitted, stdin is used.
+    \\
+    \\Options:
+    \\  -h, --help            Print this help and exit
+    \\  --color [auto|off|on] Enable or disable colored error messages
+    \\  -t                    (debug option) Output ZIR in text form to stdout
+;
+
+pub fn cmdAstCheck(
     gpa: *Allocator,
     arena: *Allocator,
     args: []const []const u8,
@@ -3572,45 +3591,91 @@ pub fn cmdAstgen(
     const AstGen = @import("AstGen.zig");
     const Zir = @import("Zir.zig");
 
-    const zig_source_file = args[0];
-
-    var f = try fs.cwd().openFile(zig_source_file, .{});
-    defer f.close();
-
-    const stat = try f.stat();
-
-    if (stat.size > max_src_size)
-        return error.FileTooBig;
+    var color: Color = .auto;
+    var want_output_text = false;
+    var have_zig_source_file = false;
+    var zig_source_file: ?[]const u8 = null;
+
+    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")) {
+                try io.getStdOut().writeAll(usage_ast_check);
+                return cleanExit();
+            } else if (mem.eql(u8, arg, "-t")) {
+                want_output_text = true;
+            } 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 {
+                fatal("unrecognized parameter: '{s}'", .{arg});
+            }
+        } else if (zig_source_file == null) {
+            zig_source_file = arg;
+        } else {
+            fatal("extra positional parameter: '{s}'", .{arg});
+        }
+    }
 
     var file: Module.Scope.File = .{
         .status = .never_loaded,
         .source_loaded = false,
         .tree_loaded = false,
         .zir_loaded = false,
-        .sub_file_path = zig_source_file,
+        .sub_file_path = undefined,
         .source = undefined,
-        .stat_size = stat.size,
-        .stat_inode = stat.inode,
-        .stat_mtime = stat.mtime,
+        .stat_size = undefined,
+        .stat_inode = undefined,
+        .stat_mtime = undefined,
         .tree = undefined,
         .zir = undefined,
         .pkg = undefined,
         .root_decl = null,
     };
-
-    const source = try arena.allocSentinel(u8, stat.size, 0);
-    const amt = try f.readAll(source);
-    if (amt != stat.size)
-        return error.UnexpectedEndOfFile;
-    file.source = source;
-    file.source_loaded = true;
+    if (zig_source_file) |file_name| {
+        var f = try fs.cwd().openFile(file_name, .{});
+        defer f.close();
+
+        const stat = try f.stat();
+
+        if (stat.size > max_src_size)
+            return error.FileTooBig;
+
+        const source = try arena.allocSentinel(u8, stat.size, 0);
+        const amt = try f.readAll(source);
+        if (amt != stat.size)
+            return error.UnexpectedEndOfFile;
+
+        file.sub_file_path = file_name;
+        file.source = source;
+        file.source_loaded = true;
+        file.stat_size = stat.size;
+        file.stat_inode = stat.inode;
+        file.stat_mtime = stat.mtime;
+    } else {
+        const stdin = io.getStdIn();
+        const source = readSourceFileToEndAlloc(arena, &stdin, null) catch |err| {
+            fatal("unable to read stdin: {s}", .{err});
+        };
+        file.sub_file_path = "<stdin>";
+        file.source = source;
+        file.source_loaded = true;
+        file.stat_size = source.len;
+    }
 
     file.tree = try std.zig.parse(gpa, file.source);
     file.tree_loaded = true;
     defer file.tree.deinit(gpa);
 
     for (file.tree.errors) |parse_error| {
-        try printErrMsgToFile(gpa, parse_error, file.tree, zig_source_file, io.getStdErr(), .auto);
+        try printErrMsgToFile(gpa, parse_error, file.tree, file.sub_file_path, io.getStdErr(), color);
     }
     if (file.tree.errors.len != 0) {
         process.exit(1);
@@ -3620,6 +3685,27 @@ pub fn cmdAstgen(
     file.zir_loaded = true;
     defer file.zir.deinit(gpa);
 
+    if (file.zir.hasCompileErrors()) {
+        var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
+        try Compilation.AllErrors.addZir(arena, &errors, &file);
+        const ttyconf: std.debug.TTY.Config = switch (color) {
+            .auto => std.debug.detectTTYConfig(),
+            .on => .escape_codes,
+            .off => .no_color,
+        };
+        for (errors.items) |full_err_msg| {
+            full_err_msg.renderToStdErr(ttyconf);
+        }
+        process.exit(1);
+    }
+
+    if (!want_output_text) {
+        return cleanExit();
+    }
+    if (!debug_extensions_enabled) {
+        fatal("-t option only available in debug builds of zig", .{});
+    }
+
     {
         const token_bytes = @sizeOf(std.zig.ast.TokenList) +
             file.tree.tokens.len * (@sizeOf(std.zig.Token.Tag) + @sizeOf(std.zig.ast.ByteOffset));
@@ -3647,7 +3733,7 @@ pub fn cmdAstgen(
             \\# Extra Data Items:   {d} ({})
             \\
         , .{
-            fmtIntSizeBin(source.len),
+            fmtIntSizeBin(file.source.len),
             file.tree.tokens.len, fmtIntSizeBin(token_bytes),
             file.tree.nodes.len, fmtIntSizeBin(tree_bytes),
             fmtIntSizeBin(total_bytes),
@@ -3658,16 +3744,6 @@ pub fn cmdAstgen(
         // zig fmt: on
     }
 
-    if (file.zir.hasCompileErrors()) {
-        var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
-        try Compilation.AllErrors.addZir(arena, &errors, &file);
-        const ttyconf = std.debug.detectTTYConfig();
-        for (errors.items) |full_err_msg| {
-            full_err_msg.renderToStdErr(ttyconf);
-        }
-        process.exit(1);
-    }
-
     return Zir.renderAsTextToFile(gpa, &file, io.getStdOut());
 }