diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig new file mode 100644 index 0000000000..9905b8e3a6 --- /dev/null +++ b/src-self-hosted/errmsg.zig @@ -0,0 +1,87 @@ +const std = @import("std"); +const mem = std.mem; +const os = std.os; +const Token = std.zig.Token; +const ast = std.zig.ast; +const TokenIndex = std.zig.ast.TokenIndex; + +pub const Color = enum { + Auto, + Off, + On, +}; + +pub const Msg = struct { + path: []const u8, + text: []u8, + first_token: TokenIndex, + last_token: TokenIndex, + tree: &ast.Tree, +}; + +/// `path` must outlive the returned Msg +/// `tree` must outlive the returned Msg +/// Caller owns returned Msg and must free with `allocator` +pub fn createFromParseError( + allocator: &mem.Allocator, + parse_error: &const ast.Error, + tree: &ast.Tree, + path: []const u8, +) !&Msg { + const loc_token = parse_error.loc(); + var text_buf = try std.Buffer.initSize(allocator, 0); + defer text_buf.deinit(); + + var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; + try parse_error.render(&tree.tokens, out_stream); + + const msg = try allocator.construct(Msg{ + .tree = tree, + .path = path, + .text = text_buf.toOwnedSlice(), + .first_token = loc_token, + .last_token = loc_token, + }); + errdefer allocator.destroy(msg); + + return msg; +} + +pub fn printToStream(stream: var, msg: &const Msg, color_on: bool) !void { + const first_token = msg.tree.tokens.at(msg.first_token); + const last_token = msg.tree.tokens.at(msg.last_token); + const start_loc = msg.tree.tokenLocationPtr(0, first_token); + const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token); + if (!color_on) { + try stream.print( + "{}:{}:{}: error: {}\n", + msg.path, + start_loc.line + 1, + start_loc.column + 1, + msg.text, + ); + return; + } + + try stream.print( + "{}:{}:{}: error: {}\n{}\n", + msg.path, + start_loc.line + 1, + start_loc.column + 1, + msg.text, + msg.tree.source[start_loc.line_start..start_loc.line_end], + ); + try stream.writeByteNTimes(' ', start_loc.column); + try stream.writeByteNTimes('~', last_token.end - first_token.start); + try stream.write("\n"); +} + +pub fn printToFile(file: &os.File, msg: &const Msg, color: Color) !void { + const color_on = switch (color) { + Color.Auto => file.isTty(), + Color.On => true, + Color.Off => false, + }; + var stream = &std.io.FileOutStream.init(file).stream; + return printToStream(stream, msg, color_on); +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index e8a0020e5a..734c3911ae 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -15,7 +15,9 @@ const Args = arg.Args; const Flag = arg.Flag; const Module = @import("module.zig").Module; const Target = @import("target.zig").Target; +const errmsg = @import("errmsg.zig"); +var stderr_file: os.File = undefined; var stderr: &io.OutStream(io.FileOutStream.Error) = undefined; var stdout: &io.OutStream(io.FileOutStream.Error) = undefined; @@ -51,7 +53,7 @@ pub fn main() !void { var stdout_out_stream = std.io.FileOutStream.init(&stdout_file); stdout = &stdout_out_stream.stream; - var stderr_file = try std.io.getStdErr(); + stderr_file = try std.io.getStdErr(); var stderr_out_stream = std.io.FileOutStream.init(&stderr_file); stderr = &stderr_out_stream.stream; @@ -440,18 +442,19 @@ fn buildOutputType(allocator: &Allocator, args: []const []const u8, out_type: Mo build_mode = builtin.Mode.ReleaseSafe; } - var color = Module.ErrColor.Auto; - if (flags.single("color")) |color_flag| { - if (mem.eql(u8, color_flag, "auto")) { - color = Module.ErrColor.Auto; - } else if (mem.eql(u8, color_flag, "on")) { - color = Module.ErrColor.On; - } else if (mem.eql(u8, color_flag, "off")) { - color = Module.ErrColor.Off; + const color = blk: { + if (flags.single("color")) |color_flag| { + if (mem.eql(u8, color_flag, "auto")) { + break :blk errmsg.Color.Auto; + } else if (mem.eql(u8, color_flag, "on")) { + break :blk errmsg.Color.On; + } else if (mem.eql(u8, color_flag, "off")) { + break :blk errmsg.Color.Off; + } else unreachable; } else { - unreachable; + break :blk errmsg.Color.Auto; } - } + }; var emit_type = Module.Emit.Binary; if (flags.single("emit")) |emit_flag| { @@ -687,7 +690,14 @@ const usage_fmt = \\ ; -const args_fmt_spec = []Flag{Flag.Bool("--help")}; +const args_fmt_spec = []Flag{ + Flag.Bool("--help"), + Flag.Option("--color", []const []const u8{ + "auto", + "off", + "on", + }), +}; fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void { var flags = try Args.parse(allocator, args_fmt_spec, args); @@ -703,6 +713,20 @@ fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void { os.exit(1); } + const color = blk: { + if (flags.single("color")) |color_flag| { + if (mem.eql(u8, color_flag, "auto")) { + break :blk errmsg.Color.Auto; + } else if (mem.eql(u8, color_flag, "on")) { + break :blk errmsg.Color.On; + } else if (mem.eql(u8, color_flag, "off")) { + break :blk errmsg.Color.Off; + } else unreachable; + } else { + break :blk errmsg.Color.Auto; + } + }; + for (flags.positionals.toSliceConst()) |file_path| { var file = try os.File.openRead(allocator, file_path); defer file.close(); @@ -721,25 +745,10 @@ fn cmdFmt(allocator: &Allocator, args: []const []const u8) !void { var error_it = tree.errors.iterator(0); while (error_it.next()) |parse_error| { - const token = tree.tokens.at(parse_error.loc()); - const loc = tree.tokenLocation(0, parse_error.loc()); - try stderr.print("{}:{}:{}: error: ", file_path, loc.line + 1, loc.column + 1); - try tree.renderError(parse_error, stderr); - try stderr.print("\n{}\n", source_code[loc.line_start..loc.line_end]); - { - var i: usize = 0; - while (i < loc.column) : (i += 1) { - try stderr.write(" "); - } - } - { - const caret_count = token.end - token.start; - var i: usize = 0; - while (i < caret_count) : (i += 1) { - try stderr.write("~"); - } - } - try stderr.write("\n"); + const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, file_path); + defer allocator.destroy(msg); + + try errmsg.printToFile(&stderr_file, msg, color); } if (tree.errors.len != 0) { continue; diff --git a/src-self-hosted/module.zig b/src-self-hosted/module.zig index 848ce95309..61834eab66 100644 --- a/src-self-hosted/module.zig +++ b/src-self-hosted/module.zig @@ -10,6 +10,7 @@ const Target = @import("target.zig").Target; const warn = std.debug.warn; const Token = std.zig.Token; const ArrayList = std.ArrayList; +const errmsg = @import("errmsg.zig"); pub const Module = struct { allocator: &mem.Allocator, @@ -55,7 +56,7 @@ pub const Module = struct { link_libs_list: ArrayList(&LinkLib), libc_link_lib: ?&LinkLib, - err_color: ErrColor, + err_color: errmsg.Color, verbose_tokenize: bool, verbose_ast_tree: bool, @@ -87,12 +88,6 @@ pub const Module = struct { Obj, }; - pub const ErrColor = enum { - Auto, - Off, - On, - }; - pub const LinkLib = struct { name: []const u8, path: ?[]const u8, @@ -195,7 +190,7 @@ pub const Module = struct { .windows_subsystem_console = false, .link_libs_list = ArrayList(&LinkLib).init(allocator), .libc_link_lib = null, - .err_color = ErrColor.Auto, + .err_color = errmsg.Color.Auto, .darwin_frameworks = [][]const u8{}, .darwin_version_min = DarwinVersionMin.None, .test_filters = [][]const u8{}, diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 3e1b4fe16a..56d4f9c393 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -120,7 +120,7 @@ pub const Error = union(enum) { ExpectedToken: ExpectedToken, ExpectedCommaOrEnd: ExpectedCommaOrEnd, - pub fn render(self: &Error, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const Error, tokens: &Tree.TokenList, stream: var) !void { switch (self.*) { // TODO https://github.com/ziglang/zig/issues/683 @TagType(Error).InvalidToken => |*x| return x.render(tokens, stream), @@ -145,7 +145,7 @@ pub const Error = union(enum) { } } - pub fn loc(self: &Error) TokenIndex { + pub fn loc(self: &const Error) TokenIndex { switch (self.*) { // TODO https://github.com/ziglang/zig/issues/683 @TagType(Error).InvalidToken => |x| return x.token, @@ -190,7 +190,7 @@ pub const Error = union(enum) { pub const ExpectedCall = struct { node: &Node, - pub fn render(self: &ExpectedCall, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const ExpectedCall, tokens: &Tree.TokenList, stream: var) !void { return stream.print("expected " ++ @tagName(@TagType(Node.SuffixOp.Op).Call) ++ ", found {}", @tagName(self.node.id)); } }; @@ -198,7 +198,7 @@ pub const Error = union(enum) { pub const ExpectedCallOrFnProto = struct { node: &Node, - pub fn render(self: &ExpectedCallOrFnProto, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const ExpectedCallOrFnProto, tokens: &Tree.TokenList, stream: var) !void { return stream.print("expected " ++ @tagName(@TagType(Node.SuffixOp.Op).Call) ++ " or " ++ @tagName(Node.Id.FnProto) ++ ", found {}", @tagName(self.node.id)); } }; @@ -207,7 +207,7 @@ pub const Error = union(enum) { token: TokenIndex, expected_id: @TagType(Token.Id), - pub fn render(self: &ExpectedToken, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const ExpectedToken, tokens: &Tree.TokenList, stream: var) !void { const token_name = @tagName(tokens.at(self.token).id); return stream.print("expected {}, found {}", @tagName(self.expected_id), token_name); } @@ -217,7 +217,7 @@ pub const Error = union(enum) { token: TokenIndex, end_id: @TagType(Token.Id), - pub fn render(self: &ExpectedCommaOrEnd, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const ExpectedCommaOrEnd, tokens: &Tree.TokenList, stream: var) !void { const token_name = @tagName(tokens.at(self.token).id); return stream.print("expected ',' or {}, found {}", @tagName(self.end_id), token_name); } @@ -229,7 +229,7 @@ pub const Error = union(enum) { token: TokenIndex, - pub fn render(self: &ThisError, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const ThisError, tokens: &Tree.TokenList, stream: var) !void { const token_name = @tagName(tokens.at(self.token).id); return stream.print(msg, token_name); } @@ -242,7 +242,7 @@ pub const Error = union(enum) { token: TokenIndex, - pub fn render(self: &ThisError, tokens: &Tree.TokenList, stream: var) !void { + pub fn render(self: &const ThisError, tokens: &Tree.TokenList, stream: var) !void { return stream.write(msg); } };