-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
305 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
const std = @import("std"); | ||
const builtin = @import("builtin"); | ||
|
||
const os = std.os; | ||
const io = std.io; | ||
const mem = std.mem; | ||
const Allocator = mem.Allocator; | ||
const ArrayList = std.ArrayList; | ||
const Buffer = std.Buffer; | ||
const ast = std.zig.ast; | ||
|
||
const arg = @import("fmt/arg.zig"); | ||
const self_hosted_main = @import("fmt/main.zig"); | ||
const Args = arg.Args; | ||
const Flag = arg.Flag; | ||
const errmsg = @import("fmt/errmsg.zig"); | ||
|
||
var stderr_file: os.File = undefined; | ||
var stderr: *io.OutStream(os.File.WriteError) = undefined; | ||
var stdout: *io.OutStream(os.File.WriteError) = undefined; | ||
|
||
// This brings `zig fmt` to stage 1. | ||
pub fn main() !void { | ||
// Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived, | ||
// one shot program. We don't need to waste time freeing memory and finding places to squish | ||
// bytes into. So we free everything all at once at the very end. | ||
var direct_allocator = std.heap.DirectAllocator.init(); | ||
var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator); | ||
const allocator = &arena.allocator; | ||
|
||
var stdout_file = try std.io.getStdOut(); | ||
var stdout_out_stream = stdout_file.outStream(); | ||
stdout = &stdout_out_stream.stream; | ||
|
||
stderr_file = try std.io.getStdErr(); | ||
var stderr_out_stream = stderr_file.outStream(); | ||
stderr = &stderr_out_stream.stream; | ||
const args = try std.os.argsAlloc(allocator); | ||
|
||
var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args); | ||
defer flags.deinit(); | ||
|
||
if (flags.present("help")) { | ||
try stdout.write(self_hosted_main.usage_fmt); | ||
os.exit(0); | ||
} | ||
|
||
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; | ||
} | ||
}; | ||
|
||
if (flags.present("stdin")) { | ||
if (flags.positionals.len != 0) { | ||
try stderr.write("cannot use --stdin with positional arguments\n"); | ||
os.exit(1); | ||
} | ||
|
||
var stdin_file = try io.getStdIn(); | ||
var stdin = stdin_file.inStream(); | ||
|
||
const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size); | ||
defer allocator.free(source_code); | ||
|
||
var tree = std.zig.parse(allocator, source_code) catch |err| { | ||
try stderr.print("error parsing stdin: {}\n", err); | ||
os.exit(1); | ||
}; | ||
defer tree.deinit(); | ||
|
||
var error_it = tree.errors.iterator(0); | ||
while (error_it.next()) |parse_error| { | ||
try printErrMsgToFile(allocator, parse_error, &tree, "<stdin>", stderr_file, color); | ||
} | ||
if (tree.errors.len != 0) { | ||
os.exit(1); | ||
} | ||
if (flags.present("check")) { | ||
const anything_changed = try std.zig.render(allocator, io.null_out_stream, &tree); | ||
const code = if (anything_changed) u8(1) else u8(0); | ||
os.exit(code); | ||
} | ||
|
||
_ = try std.zig.render(allocator, stdout, &tree); | ||
return; | ||
} | ||
|
||
if (flags.positionals.len == 0) { | ||
try stderr.write("expected at least one source file argument\n"); | ||
os.exit(1); | ||
} | ||
|
||
if (flags.positionals.len == 0) { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
try stderr.write("expected at least one source file argument\n"); | ||
os.exit(1); | ||
} | ||
|
||
var fmt = Fmt{ | ||
.seen = Fmt.SeenMap.init(allocator), | ||
.any_error = false, | ||
.color = color, | ||
.allocator = allocator, | ||
}; | ||
|
||
const check_mode = flags.present("check"); | ||
|
||
for (flags.positionals.toSliceConst()) |file_path| { | ||
try fmtPath(&fmt, file_path, check_mode); | ||
} | ||
if (fmt.any_error) { | ||
os.exit(1); | ||
} | ||
} | ||
|
||
const FmtError = error{ | ||
SystemResources, | ||
OperationAborted, | ||
IoPending, | ||
BrokenPipe, | ||
Unexpected, | ||
WouldBlock, | ||
FileClosed, | ||
DestinationAddressRequired, | ||
DiskQuota, | ||
FileTooBig, | ||
InputOutput, | ||
NoSpaceLeft, | ||
AccessDenied, | ||
OutOfMemory, | ||
RenameAcrossMountPoints, | ||
ReadOnlyFileSystem, | ||
LinkQuotaExceeded, | ||
FileBusy, | ||
} || os.File.OpenError; | ||
|
||
fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { | ||
const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); | ||
defer fmt.allocator.free(file_path); | ||
|
||
if (try fmt.seen.put(file_path, {})) |_| return; | ||
|
||
const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { | ||
error.IsDir, error.AccessDenied => { | ||
// TODO make event based (and dir.next()) | ||
var dir = try std.os.Dir.open(fmt.allocator, file_path); | ||
defer dir.close(); | ||
|
||
while (try dir.next()) |entry| { | ||
if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { | ||
const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); | ||
try fmtPath(fmt, full_path, check_mode); | ||
} | ||
} | ||
return; | ||
}, | ||
else => { | ||
// TODO lock stderr printing | ||
try stderr.print("unable to open '{}': {}\n", file_path, err); | ||
fmt.any_error = true; | ||
return; | ||
}, | ||
}; | ||
defer fmt.allocator.free(source_code); | ||
|
||
var tree = std.zig.parse(fmt.allocator, source_code) catch |err| { | ||
try stderr.print("error parsing file '{}': {}\n", file_path, err); | ||
fmt.any_error = true; | ||
return; | ||
}; | ||
defer tree.deinit(); | ||
|
||
var error_it = tree.errors.iterator(0); | ||
while (error_it.next()) |parse_error| { | ||
try printErrMsgToFile(fmt.allocator, parse_error, &tree, file_path, stderr_file, fmt.color); | ||
} | ||
if (tree.errors.len != 0) { | ||
fmt.any_error = true; | ||
return; | ||
} | ||
|
||
if (check_mode) { | ||
const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, &tree); | ||
if (anything_changed) { | ||
try stderr.print("{}\n", file_path); | ||
fmt.any_error = true; | ||
} | ||
} else { | ||
// TODO make this evented | ||
const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); | ||
defer baf.destroy(); | ||
|
||
const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), &tree); | ||
if (anything_changed) { | ||
try stderr.print("{}\n", file_path); | ||
try baf.finish(); | ||
} | ||
} | ||
} | ||
|
||
const Fmt = struct { | ||
seen: SeenMap, | ||
any_error: bool, | ||
color: errmsg.Color, | ||
allocator: *mem.Allocator, | ||
|
||
const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); | ||
}; | ||
|
||
fn printErrMsgToFile(allocator: *mem.Allocator, parse_error: *const ast.Error, tree: *ast.Tree, | ||
path: []const u8, file: os.File, color: errmsg.Color,) !void | ||
{ | ||
const color_on = switch (color) { | ||
errmsg.Color.Auto => file.isTty(), | ||
errmsg.Color.On => true, | ||
errmsg.Color.Off => false, | ||
}; | ||
const lok_token = parse_error.loc(); | ||
const span = errmsg.Span{ | ||
.first = lok_token, | ||
.last = lok_token, | ||
}; | ||
|
||
const first_token = tree.tokens.at(span.first); | ||
const last_token = tree.tokens.at(span.last); | ||
const start_loc = tree.tokenLocationPtr(0, first_token); | ||
const end_loc = tree.tokenLocationPtr(first_token.end, last_token); | ||
|
||
var text_buf = try std.Buffer.initSize(allocator, 0); | ||
var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; | ||
try parse_error.render(&tree.tokens, out_stream); | ||
const text = text_buf.toOwnedSlice(); | ||
|
||
const stream = &file.outStream().stream; | ||
if (!color_on) { | ||
try stream.print( | ||
"{}:{}:{}: error: {}\n", | ||
path, | ||
start_loc.line + 1, | ||
start_loc.column + 1, | ||
text, | ||
); | ||
return; | ||
} | ||
|
||
try stream.print( | ||
"{}:{}:{}: error: {}\n{}\n", | ||
path, | ||
start_loc.line + 1, | ||
start_loc.column + 1, | ||
text, | ||
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"); | ||
} |
Redundant check here.