authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-05-30 18:26:09-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-05-30 18:26:09-04:00
loga05acaf9fd8ea1b42ec300ce4ba948ac00b89d76
tree5d10ed6f6c238c6d8ffd56f9f0df2042c01aab99
parentd8699ae57ed1e69aac209d674b26685c6c569525

Add --color CLI option to zig fmt

It doesn't actually do terminal color yet because we need to add cross platform terminal color abstractions. But it toggles between the single line error reporting and the multiline error reporting. See #1026

4 files changed, 138 insertions(+), 47 deletions(-)

src-self-hosted/errmsg.zig created+87
......@@ -0,0 +1,87 @@
1const std = @import("std");
2const mem = std.mem;
3const os = std.os;
4const Token = std.zig.Token;
5const ast = std.zig.ast;
6const TokenIndex = std.zig.ast.TokenIndex;
7
8pub const Color = enum {
9 Auto,
10 Off,
11 On,
12};
13
14pub const Msg = struct {
15 path: []const u8,
16 text: []u8,
17 first_token: TokenIndex,
18 last_token: TokenIndex,
19 tree: &ast.Tree,
20};
21
22/// `path` must outlive the returned Msg
23/// `tree` must outlive the returned Msg
24/// Caller owns returned Msg and must free with `allocator`
25pub fn createFromParseError(
26 allocator: &mem.Allocator,
27 parse_error: &const ast.Error,
28 tree: &ast.Tree,
29 path: []const u8,
30) !&Msg {
31 const loc_token = parse_error.loc();
32 var text_buf = try std.Buffer.initSize(allocator, 0);
33 defer text_buf.deinit();
34
35 var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
36 try parse_error.render(&tree.tokens, out_stream);
37
38 const msg = try allocator.construct(Msg{
39 .tree = tree,
40 .path = path,
41 .text = text_buf.toOwnedSlice(),
42 .first_token = loc_token,
43 .last_token = loc_token,
44 });
45 errdefer allocator.destroy(msg);
46
47 return msg;
48}
49
50pub fn printToStream(stream: var, msg: &const Msg, color_on: bool) !void {
51 const first_token = msg.tree.tokens.at(msg.first_token);
52 const last_token = msg.tree.tokens.at(msg.last_token);
53 const start_loc = msg.tree.tokenLocationPtr(0, first_token);
54 const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token);
55 if (!color_on) {
56 try stream.print(
57 "{}:{}:{}: error: {}\n",
58 msg.path,
59 start_loc.line + 1,
60 start_loc.column + 1,
61 msg.text,
62 );
63 return;
64 }
65
66 try stream.print(
67 "{}:{}:{}: error: {}\n{}\n",
68 msg.path,
69 start_loc.line + 1,
70 start_loc.column + 1,
71 msg.text,
72 msg.tree.source[start_loc.line_start..start_loc.line_end],
73 );
74 try stream.writeByteNTimes(' ', start_loc.column);
75 try stream.writeByteNTimes('~', last_token.end - first_token.start);
76 try stream.write("\n");
77}
78
79pub fn printToFile(file: &os.File, msg: &const Msg, color: Color) !void {
80 const color_on = switch (color) {
81 Color.Auto => file.isTty(),
82 Color.On => true,
83 Color.Off => false,
84 };
85 var stream = &std.io.FileOutStream.init(file).stream;
86 return printToStream(stream, msg, color_on);
87}
src-self-hosted/main.zig+40-31
......@@ -15,7 +15,9 @@ const Args = arg.Args;
1515const Flag = arg.Flag;
1616const Module = @import("module.zig").Module;
1717const Target = @import("target.zig").Target;
18const errmsg = @import("errmsg.zig");
1819
20var stderr_file: os.File = undefined;
1921var stderr: &io.OutStream(io.FileOutStream.Error) = undefined;
2022var stdout: &io.OutStream(io.FileOutStream.Error) = undefined;
2123
......@@ -51,7 +53,7 @@ pub fn main() !void {
5153 var stdout_out_stream = std.io.FileOutStream.init(&stdout_file);
5254 stdout = &stdout_out_stream.stream;
5355
54 var stderr_file = try std.io.getStdErr();
56 stderr_file = try std.io.getStdErr();
5557 var stderr_out_stream = std.io.FileOutStream.init(&stderr_file);
5658 stderr = &stderr_out_stream.stream;
5759
......@@ -440,18 +442,19 @@ fn buildOutputType(allocator: &Allocator, args: []const []const u8, out_type: Mo
440442 build_mode = builtin.Mode.ReleaseSafe;
441443 }
442444
443 var color = Module.ErrColor.Auto;
444 if (flags.single("color")) |color_flag| {
445 if (mem.eql(u8, color_flag, "auto")) {
446 color = Module.ErrColor.Auto;
447 } else if (mem.eql(u8, color_flag, "on")) {
448 color = Module.ErrColor.On;
449 } else if (mem.eql(u8, color_flag, "off")) {
450 color = Module.ErrColor.Off;
445 const color = blk: {
446 if (flags.single("color")) |color_flag| {
447 if (mem.eql(u8, color_flag, "auto")) {
448 break :blk errmsg.Color.Auto;
449 } else if (mem.eql(u8, color_flag, "on")) {
450 break :blk errmsg.Color.On;
451 } else if (mem.eql(u8, color_flag, "off")) {
452 break :blk errmsg.Color.Off;
453 } else unreachable;
451454 } else {
452 unreachable;
455 break :blk errmsg.Color.Auto;
453456 }
454 }
457 };
455458
456459 var emit_type = Module.Emit.Binary;
457460 if (flags.single("emit")) |emit_flag| {
......@@ -687,7 +690,14 @@ const usage_fmt =
687690 \\
688691;
689692
690const args_fmt_spec = []Flag{Flag.Bool("--help")};
693const args_fmt_spec = []Flag{
694 Flag.Bool("--help"),
695 Flag.Option("--color", []const []const u8{
696 "auto",
697 "off",
698 "on",
699 }),
700};
691701
692702fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void {
693703 var flags = try Args.parse(allocator, args_fmt_spec, args);
......@@ -703,6 +713,20 @@ fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void {
703713 os.exit(1);
704714 }
705715
716 const color = blk: {
717 if (flags.single("color")) |color_flag| {
718 if (mem.eql(u8, color_flag, "auto")) {
719 break :blk errmsg.Color.Auto;
720 } else if (mem.eql(u8, color_flag, "on")) {
721 break :blk errmsg.Color.On;
722 } else if (mem.eql(u8, color_flag, "off")) {
723 break :blk errmsg.Color.Off;
724 } else unreachable;
725 } else {
726 break :blk errmsg.Color.Auto;
727 }
728 };
729
706730 for (flags.positionals.toSliceConst()) |file_path| {
707731 var file = try os.File.openRead(allocator, file_path);
708732 defer file.close();
......@@ -721,25 +745,10 @@ fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void {
721745
722746 var error_it = tree.errors.iterator(0);
723747 while (error_it.next()) |parse_error| {
724 const token = tree.tokens.at(parse_error.loc());
725 const loc = tree.tokenLocation(0, parse_error.loc());
726 try stderr.print("{}:{}:{}: error: ", file_path, loc.line + 1, loc.column + 1);
727 try tree.renderError(parse_error, stderr);
728 try stderr.print("\n{}\n", source_code[loc.line_start..loc.line_end]);
729 {
730 var i: usize = 0;
731 while (i < loc.column) : (i += 1) {
732 try stderr.write(" ");
733 }
734 }
735 {
736 const caret_count = token.end - token.start;
737 var i: usize = 0;
738 while (i < caret_count) : (i += 1) {
739 try stderr.write("~");
740 }
741 }
742 try stderr.write("\n");
748 const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, file_path);
749 defer allocator.destroy(msg);
750
751 try errmsg.printToFile(&stderr_file, msg, color);
743752 }
744753 if (tree.errors.len != 0) {
745754 continue;
src-self-hosted/module.zig+3-8
......@@ -10,6 +10,7 @@ const Target = @import("target.zig").Target;
1010const warn = std.debug.warn;
1111const Token = std.zig.Token;
1212const ArrayList = std.ArrayList;
13const errmsg = @import("errmsg.zig");
1314
1415pub const Module = struct {
1516 allocator: &mem.Allocator,
......@@ -55,7 +56,7 @@ pub const Module = struct {
5556 link_libs_list: ArrayList(&LinkLib),
5657 libc_link_lib: ?&LinkLib,
5758
58 err_color: ErrColor,
59 err_color: errmsg.Color,
5960
6061 verbose_tokenize: bool,
6162 verbose_ast_tree: bool,
......@@ -87,12 +88,6 @@ pub const Module = struct {
8788 Obj,
8889 };
8990
90 pub const ErrColor = enum {
91 Auto,
92 Off,
93 On,
94 };
95
9691 pub const LinkLib = struct {
9792 name: []const u8,
9893 path: ?[]const u8,
......@@ -195,7 +190,7 @@ pub const Module = struct {
195190 .windows_subsystem_console = false,
196191 .link_libs_list = ArrayList(&LinkLib).init(allocator),
197192 .libc_link_lib = null,
198 .err_color = ErrColor.Auto,
193 .err_color = errmsg.Color.Auto,
199194 .darwin_frameworks = [][]const u8{},
200195 .darwin_version_min = DarwinVersionMin.None,
201196 .test_filters = [][]const u8{},
std/zig/ast.zig+8-8
......@@ -120,7 +120,7 @@ pub const Error = union(enum) {
120120 ExpectedToken: ExpectedToken,
121121 ExpectedCommaOrEnd: ExpectedCommaOrEnd,
122122
123 pub fn render(self: &Error, tokens: &Tree.TokenList, stream: var) !void {
123 pub fn render(self: &const Error, tokens: &Tree.TokenList, stream: var) !void {
124124 switch (self.*) {
125125 // TODO https://github.com/ziglang/zig/issues/683
126126 @TagType(Error).InvalidToken => |*x| return x.render(tokens, stream),
......@@ -145,7 +145,7 @@ pub const Error = union(enum) {
145145 }
146146 }
147147
148 pub fn loc(self: &Error) TokenIndex {
148 pub fn loc(self: &const Error) TokenIndex {
149149 switch (self.*) {
150150 // TODO https://github.com/ziglang/zig/issues/683
151151 @TagType(Error).InvalidToken => |x| return x.token,
......@@ -190,7 +190,7 @@ pub const Error = union(enum) {
190190 pub const ExpectedCall = struct {
191191 node: &Node,
192192
193 pub fn render(self: &ExpectedCall, tokens: &Tree.TokenList, stream: var) !void {
193 pub fn render(self: &const ExpectedCall, tokens: &Tree.TokenList, stream: var) !void {
194194 return stream.print("expected " ++ @tagName(@TagType(Node.SuffixOp.Op).Call) ++ ", found {}", @tagName(self.node.id));
195195 }
196196 };
......@@ -198,7 +198,7 @@ pub const Error = union(enum) {
198198 pub const ExpectedCallOrFnProto = struct {
199199 node: &Node,
200200
201 pub fn render(self: &ExpectedCallOrFnProto, tokens: &Tree.TokenList, stream: var) !void {
201 pub fn render(self: &const ExpectedCallOrFnProto, tokens: &Tree.TokenList, stream: var) !void {
202202 return stream.print("expected " ++ @tagName(@TagType(Node.SuffixOp.Op).Call) ++ " or " ++ @tagName(Node.Id.FnProto) ++ ", found {}", @tagName(self.node.id));
203203 }
204204 };
......@@ -207,7 +207,7 @@ pub const Error = union(enum) {
207207 token: TokenIndex,
208208 expected_id: @TagType(Token.Id),
209209
210 pub fn render(self: &ExpectedToken, tokens: &Tree.TokenList, stream: var) !void {
210 pub fn render(self: &const ExpectedToken, tokens: &Tree.TokenList, stream: var) !void {
211211 const token_name = @tagName(tokens.at(self.token).id);
212212 return stream.print("expected {}, found {}", @tagName(self.expected_id), token_name);
213213 }
......@@ -217,7 +217,7 @@ pub const Error = union(enum) {
217217 token: TokenIndex,
218218 end_id: @TagType(Token.Id),
219219
220 pub fn render(self: &ExpectedCommaOrEnd, tokens: &Tree.TokenList, stream: var) !void {
220 pub fn render(self: &const ExpectedCommaOrEnd, tokens: &Tree.TokenList, stream: var) !void {
221221 const token_name = @tagName(tokens.at(self.token).id);
222222 return stream.print("expected ',' or {}, found {}", @tagName(self.end_id), token_name);
223223 }
......@@ -229,7 +229,7 @@ pub const Error = union(enum) {
229229
230230 token: TokenIndex,
231231
232 pub fn render(self: &ThisError, tokens: &Tree.TokenList, stream: var) !void {
232 pub fn render(self: &const ThisError, tokens: &Tree.TokenList, stream: var) !void {
233233 const token_name = @tagName(tokens.at(self.token).id);
234234 return stream.print(msg, token_name);
235235 }
......@@ -242,7 +242,7 @@ pub const Error = union(enum) {
242242
243243 token: TokenIndex,
244244
245 pub fn render(self: &ThisError, tokens: &Tree.TokenList, stream: var) !void {
245 pub fn render(self: &const ThisError, tokens: &Tree.TokenList, stream: var) !void {
246246 return stream.write(msg);
247247 }
248248 };