Skip to content

Commit ba56f36

Browse files
committed
bring zig fmt to stage1
1 parent 77a4e7b commit ba56f36

File tree

4 files changed

+305
-7
lines changed

4 files changed

+305
-7
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ set(ZIG_STD_FILES
652652
"special/compiler_rt/udivmodti4.zig"
653653
"special/compiler_rt/udivti3.zig"
654654
"special/compiler_rt/umodti3.zig"
655+
"special/fmt_runner.zig"
655656
"special/init-exe/build.zig"
656657
"special/init-exe/src/main.zig"
657658
"special/init-lib/build.zig"
@@ -905,3 +906,7 @@ foreach(file ${ZIG_STD_FILES})
905906
get_filename_component(file_dir "${ZIG_STD_DEST}/${file}" DIRECTORY)
906907
install(FILES "${CMAKE_SOURCE_DIR}/std/${file}" DESTINATION "${file_dir}")
907908
endforeach()
909+
910+
install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/arg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
911+
install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/main.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
912+
install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/errmsg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")

src-self-hosted/main.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var stderr_file: os.File = undefined;
2424
var stderr: *io.OutStream(os.File.WriteError) = undefined;
2525
var stdout: *io.OutStream(os.File.WriteError) = undefined;
2626

27-
const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
27+
pub const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
2828

2929
const usage =
3030
\\usage: zig [command] [options]
@@ -510,7 +510,7 @@ fn cmdBuildObj(allocator: *Allocator, args: []const []const u8) !void {
510510
return buildOutputType(allocator, args, Compilation.Kind.Obj);
511511
}
512512

513-
const usage_fmt =
513+
pub const usage_fmt =
514514
\\usage: zig fmt [file]...
515515
\\
516516
\\ Formats the input files and modifies them in-place.
@@ -527,7 +527,7 @@ const usage_fmt =
527527
\\
528528
;
529529

530-
const args_fmt_spec = []Flag{
530+
pub const args_fmt_spec = []Flag{
531531
Flag.Bool("--help"),
532532
Flag.Bool("--check"),
533533
Flag.Option("--color", []const []const u8{

src/main.cpp

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ static int print_error_usage(const char *arg0) {
2121
return EXIT_FAILURE;
2222
}
2323

24-
static int print_full_usage(const char *arg0) {
25-
fprintf(stdout,
24+
static int print_full_usage(const char *arg0, FILE *file, int return_code) {
25+
fprintf(file,
2626
"Usage: %s [command] [options]\n"
2727
"\n"
2828
"Commands:\n"
@@ -31,6 +31,7 @@ static int print_full_usage(const char *arg0) {
3131
" build-lib [source] create library from source or object files\n"
3232
" build-obj [source] create object from source or assembly\n"
3333
" builtin show the source code of that @import(\"builtin\")\n"
34+
" fmt parse files and render in canonical zig format\n"
3435
" help show this usage information\n"
3536
" id print the base64-encoded compiler id\n"
3637
" init-exe initialize a `zig build` application in the cwd\n"
@@ -106,7 +107,7 @@ static int print_full_usage(const char *arg0) {
106107
" --test-cmd [arg] specify test execution command one arg at a time\n"
107108
" --test-cmd-bin appends test binary path to test cmd args\n"
108109
, arg0);
109-
return EXIT_SUCCESS;
110+
return return_code;
110111
}
111112

112113
static const char *ZIG_ZEN = "\n"
@@ -515,6 +516,31 @@ int main(int argc, char **argv) {
515516
fprintf(stderr, "\n");
516517
}
517518
return (term.how == TerminationIdClean) ? term.code : -1;
519+
} else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
520+
init_all_targets();
521+
Buf *fmt_runner_path = buf_alloc();
522+
os_path_join(get_zig_special_dir(), buf_create_from_str("fmt_runner.zig"), fmt_runner_path);
523+
CodeGen *g = codegen_create(fmt_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(),
524+
nullptr);
525+
g->is_single_threaded = true;
526+
codegen_set_out_name(g, buf_create_from_str("fmt"));
527+
g->enable_cache = true;
528+
529+
codegen_build_and_link(g);
530+
531+
ZigList<const char*> args = {0};
532+
for (int i = 2; i < argc; i += 1) {
533+
args.append(argv[i]);
534+
}
535+
args.append(nullptr);
536+
const char *exec_path = buf_ptr(&g->output_file_path);
537+
538+
os_execv(exec_path, args.items);
539+
540+
args.pop();
541+
Termination term;
542+
os_spawn_process(exec_path, args, &term);
543+
return term.code;
518544
}
519545

520546
for (int i = 1; i < argc; i += 1) {
@@ -527,6 +553,8 @@ int main(int argc, char **argv) {
527553
build_mode = BuildModeSafeRelease;
528554
} else if (strcmp(arg, "--release-small") == 0) {
529555
build_mode = BuildModeSmallRelease;
556+
} else if (strcmp(arg, "--help") == 0) {
557+
return print_full_usage(arg0, stderr, EXIT_FAILURE);
530558
} else if (strcmp(arg, "--strip") == 0) {
531559
strip = true;
532560
} else if (strcmp(arg, "--static") == 0) {
@@ -1080,7 +1108,7 @@ int main(int argc, char **argv) {
10801108
}
10811109
}
10821110
case CmdHelp:
1083-
return print_full_usage(arg0);
1111+
return print_full_usage(arg0, stdout, EXIT_SUCCESS);
10841112
case CmdVersion:
10851113
printf("%s\n", ZIG_VERSION_STRING);
10861114
return EXIT_SUCCESS;

std/special/fmt_runner.zig

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
const std = @import("std");
2+
const builtin = @import("builtin");
3+
4+
const os = std.os;
5+
const io = std.io;
6+
const mem = std.mem;
7+
const Allocator = mem.Allocator;
8+
const ArrayList = std.ArrayList;
9+
const Buffer = std.Buffer;
10+
const ast = std.zig.ast;
11+
12+
const arg = @import("fmt/arg.zig");
13+
const self_hosted_main = @import("fmt/main.zig");
14+
const Args = arg.Args;
15+
const Flag = arg.Flag;
16+
const errmsg = @import("fmt/errmsg.zig");
17+
18+
var stderr_file: os.File = undefined;
19+
var stderr: *io.OutStream(os.File.WriteError) = undefined;
20+
var stdout: *io.OutStream(os.File.WriteError) = undefined;
21+
22+
// This brings `zig fmt` to stage 1.
23+
pub fn main() !void {
24+
// Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived,
25+
// one shot program. We don't need to waste time freeing memory and finding places to squish
26+
// bytes into. So we free everything all at once at the very end.
27+
var direct_allocator = std.heap.DirectAllocator.init();
28+
var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator);
29+
const allocator = &arena.allocator;
30+
31+
var stdout_file = try std.io.getStdOut();
32+
var stdout_out_stream = stdout_file.outStream();
33+
stdout = &stdout_out_stream.stream;
34+
35+
stderr_file = try std.io.getStdErr();
36+
var stderr_out_stream = stderr_file.outStream();
37+
stderr = &stderr_out_stream.stream;
38+
const args = try std.os.argsAlloc(allocator);
39+
40+
var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args);
41+
defer flags.deinit();
42+
43+
if (flags.present("help")) {
44+
try stdout.write(self_hosted_main.usage_fmt);
45+
os.exit(0);
46+
}
47+
48+
const color = blk: {
49+
if (flags.single("color")) |color_flag| {
50+
if (mem.eql(u8, color_flag, "auto")) {
51+
break :blk errmsg.Color.Auto;
52+
} else if (mem.eql(u8, color_flag, "on")) {
53+
break :blk errmsg.Color.On;
54+
} else if (mem.eql(u8, color_flag, "off")) {
55+
break :blk errmsg.Color.Off;
56+
} else unreachable;
57+
} else {
58+
break :blk errmsg.Color.Auto;
59+
}
60+
};
61+
62+
if (flags.present("stdin")) {
63+
if (flags.positionals.len != 0) {
64+
try stderr.write("cannot use --stdin with positional arguments\n");
65+
os.exit(1);
66+
}
67+
68+
var stdin_file = try io.getStdIn();
69+
var stdin = stdin_file.inStream();
70+
71+
const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size);
72+
defer allocator.free(source_code);
73+
74+
var tree = std.zig.parse(allocator, source_code) catch |err| {
75+
try stderr.print("error parsing stdin: {}\n", err);
76+
os.exit(1);
77+
};
78+
defer tree.deinit();
79+
80+
var error_it = tree.errors.iterator(0);
81+
while (error_it.next()) |parse_error| {
82+
try printErrMsgToFile(allocator, parse_error, &tree, "<stdin>", stderr_file, color);
83+
}
84+
if (tree.errors.len != 0) {
85+
os.exit(1);
86+
}
87+
if (flags.present("check")) {
88+
const anything_changed = try std.zig.render(allocator, io.null_out_stream, &tree);
89+
const code = if (anything_changed) u8(1) else u8(0);
90+
os.exit(code);
91+
}
92+
93+
_ = try std.zig.render(allocator, stdout, &tree);
94+
return;
95+
}
96+
97+
if (flags.positionals.len == 0) {
98+
try stderr.write("expected at least one source file argument\n");
99+
os.exit(1);
100+
}
101+
102+
if (flags.positionals.len == 0) {
103+
try stderr.write("expected at least one source file argument\n");
104+
os.exit(1);
105+
}
106+
107+
var fmt = Fmt{
108+
.seen = Fmt.SeenMap.init(allocator),
109+
.any_error = false,
110+
.color = color,
111+
.allocator = allocator,
112+
};
113+
114+
const check_mode = flags.present("check");
115+
116+
for (flags.positionals.toSliceConst()) |file_path| {
117+
try fmtPath(&fmt, file_path, check_mode);
118+
}
119+
if (fmt.any_error) {
120+
os.exit(1);
121+
}
122+
}
123+
124+
const FmtError = error{
125+
SystemResources,
126+
OperationAborted,
127+
IoPending,
128+
BrokenPipe,
129+
Unexpected,
130+
WouldBlock,
131+
FileClosed,
132+
DestinationAddressRequired,
133+
DiskQuota,
134+
FileTooBig,
135+
InputOutput,
136+
NoSpaceLeft,
137+
AccessDenied,
138+
OutOfMemory,
139+
RenameAcrossMountPoints,
140+
ReadOnlyFileSystem,
141+
LinkQuotaExceeded,
142+
FileBusy,
143+
} || os.File.OpenError;
144+
145+
fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
146+
const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
147+
defer fmt.allocator.free(file_path);
148+
149+
if (try fmt.seen.put(file_path, {})) |_| return;
150+
151+
const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
152+
error.IsDir, error.AccessDenied => {
153+
// TODO make event based (and dir.next())
154+
var dir = try std.os.Dir.open(fmt.allocator, file_path);
155+
defer dir.close();
156+
157+
while (try dir.next()) |entry| {
158+
if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {
159+
const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name });
160+
try fmtPath(fmt, full_path, check_mode);
161+
}
162+
}
163+
return;
164+
},
165+
else => {
166+
// TODO lock stderr printing
167+
try stderr.print("unable to open '{}': {}\n", file_path, err);
168+
fmt.any_error = true;
169+
return;
170+
},
171+
};
172+
defer fmt.allocator.free(source_code);
173+
174+
var tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
175+
try stderr.print("error parsing file '{}': {}\n", file_path, err);
176+
fmt.any_error = true;
177+
return;
178+
};
179+
defer tree.deinit();
180+
181+
var error_it = tree.errors.iterator(0);
182+
while (error_it.next()) |parse_error| {
183+
try printErrMsgToFile(fmt.allocator, parse_error, &tree, file_path, stderr_file, fmt.color);
184+
}
185+
if (tree.errors.len != 0) {
186+
fmt.any_error = true;
187+
return;
188+
}
189+
190+
if (check_mode) {
191+
const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, &tree);
192+
if (anything_changed) {
193+
try stderr.print("{}\n", file_path);
194+
fmt.any_error = true;
195+
}
196+
} else {
197+
// TODO make this evented
198+
const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
199+
defer baf.destroy();
200+
201+
const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), &tree);
202+
if (anything_changed) {
203+
try stderr.print("{}\n", file_path);
204+
try baf.finish();
205+
}
206+
}
207+
}
208+
209+
const Fmt = struct {
210+
seen: SeenMap,
211+
any_error: bool,
212+
color: errmsg.Color,
213+
allocator: *mem.Allocator,
214+
215+
const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
216+
};
217+
218+
fn printErrMsgToFile(allocator: *mem.Allocator, parse_error: *const ast.Error, tree: *ast.Tree,
219+
path: []const u8, file: os.File, color: errmsg.Color,) !void
220+
{
221+
const color_on = switch (color) {
222+
errmsg.Color.Auto => file.isTty(),
223+
errmsg.Color.On => true,
224+
errmsg.Color.Off => false,
225+
};
226+
const lok_token = parse_error.loc();
227+
const span = errmsg.Span{
228+
.first = lok_token,
229+
.last = lok_token,
230+
};
231+
232+
const first_token = tree.tokens.at(span.first);
233+
const last_token = tree.tokens.at(span.last);
234+
const start_loc = tree.tokenLocationPtr(0, first_token);
235+
const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
236+
237+
var text_buf = try std.Buffer.initSize(allocator, 0);
238+
var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
239+
try parse_error.render(&tree.tokens, out_stream);
240+
const text = text_buf.toOwnedSlice();
241+
242+
const stream = &file.outStream().stream;
243+
if (!color_on) {
244+
try stream.print(
245+
"{}:{}:{}: error: {}\n",
246+
path,
247+
start_loc.line + 1,
248+
start_loc.column + 1,
249+
text,
250+
);
251+
return;
252+
}
253+
254+
try stream.print(
255+
"{}:{}:{}: error: {}\n{}\n",
256+
path,
257+
start_loc.line + 1,
258+
start_loc.column + 1,
259+
text,
260+
tree.source[start_loc.line_start..start_loc.line_end],
261+
);
262+
try stream.writeByteNTimes(' ', start_loc.column);
263+
try stream.writeByteNTimes('~', last_token.end - first_token.start);
264+
try stream.write("\n");
265+
}

0 commit comments

Comments
 (0)