Skip to content

Commit a05acaf

Browse files
committed
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
1 parent d8699ae commit a05acaf

File tree

4 files changed

+138
-47
lines changed

4 files changed

+138
-47
lines changed

src-self-hosted/errmsg.zig

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const std = @import("std");
2+
const mem = std.mem;
3+
const os = std.os;
4+
const Token = std.zig.Token;
5+
const ast = std.zig.ast;
6+
const TokenIndex = std.zig.ast.TokenIndex;
7+
8+
pub const Color = enum {
9+
Auto,
10+
Off,
11+
On,
12+
};
13+
14+
pub 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`
25+
pub 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+
50+
pub 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+
79+
pub 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

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const Args = arg.Args;
1515
const Flag = arg.Flag;
1616
const Module = @import("module.zig").Module;
1717
const Target = @import("target.zig").Target;
18+
const errmsg = @import("errmsg.zig");
1819

20+
var stderr_file: os.File = undefined;
1921
var stderr: &io.OutStream(io.FileOutStream.Error) = undefined;
2022
var 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

690-
const args_fmt_spec = []Flag{Flag.Bool("--help")};
693+
const args_fmt_spec = []Flag{
694+
Flag.Bool("--help"),
695+
Flag.Option("--color", []const []const u8{
696+
"auto",
697+
"off",
698+
"on",
699+
}),
700+
};
691701

692702
fn 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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const Target = @import("target.zig").Target;
1010
const warn = std.debug.warn;
1111
const Token = std.zig.Token;
1212
const ArrayList = std.ArrayList;
13+
const errmsg = @import("errmsg.zig");
1314

1415
pub 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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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,15 +190,15 @@ 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
};
197197

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
};

0 commit comments

Comments
 (0)