authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-08 13:55:11-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-08 13:55:11-07:00
log2f2224c19a5328de4cdc962192e763f6301bfd72
tree7b172e61df5c99394988b920e74d98e4ab4a410b
parent4caea5d62f1a37cf5661afdfbd47458fe6e38b5f

zig fmt: individual node and token stats per file


1 files changed, 101 insertions(+), 30 deletions(-)

src/fmt.zig+101-30
...@@ -40,18 +40,22 @@ const Fmt = struct {...@@ -40,18 +40,22 @@ const Fmt = struct {
40 io: Io,40 io: Io,
41 out_buffer: std.Io.Writer.Allocating,41 out_buffer: std.Io.Writer.Allocating,
42 stdout_writer: *Io.File.Writer,42 stdout_writer: *Io.File.Writer,
43 stats: ?*Stats,
44
45 const SeenMap = std.AutoHashMap(Io.File.INode, void);
46};
47
48const Stats = struct {
43 serializer: Serializer,49 serializer: Serializer,
44 root_struct: Serializer.Struct,50 root_struct: Serializer.Struct,
4551
46 stats: bool,52 files: u32,
47 total_tokens: u64,53 tokens: u64,
48 total_nodes: u64,54 nodes: u64,
49 /// Count per token tag index55 /// Count per token tag index
50 token_stats: []u64,56 token_stats: []u64,
51 /// Count per node tag index57 /// Count per node tag index
52 node_stats: []u64,58 node_stats: []u64,
53
54 const SeenMap = std.AutoHashMap(Io.File.INode, void);
55};59};
5660
57pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {61pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void {
...@@ -60,7 +64,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -60,7 +64,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
60 var check_flag = false;64 var check_flag = false;
61 var check_ast_flag = false;65 var check_ast_flag = false;
62 var force_zon = false;66 var force_zon = false;
63 var stats = false;67 var enable_stats = false;
6468
65 var input_files = std.array_list.Managed([]const u8).init(gpa);69 var input_files = std.array_list.Managed([]const u8).init(gpa);
66 defer input_files.deinit();70 defer input_files.deinit();
...@@ -92,7 +96,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -92,7 +96,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
92 } else if (mem.eql(u8, arg, "--ast-check")) {96 } else if (mem.eql(u8, arg, "--ast-check")) {
93 check_ast_flag = true;97 check_ast_flag = true;
94 } else if (mem.eql(u8, arg, "--stats")) {98 } else if (mem.eql(u8, arg, "--stats")) {
95 stats = true;99 enable_stats = true;
96 } else if (mem.eql(u8, arg, "--exclude")) {100 } else if (mem.eql(u8, arg, "--exclude")) {
97 if (i + 1 >= args.len) {101 if (i + 1 >= args.len) {
98 fatal("expected parameter after --exclude", .{});102 fatal("expected parameter after --exclude", .{});
...@@ -192,21 +196,25 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -192,21 +196,25 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
192 .color = color,196 .color = color,
193 .out_buffer = .init(gpa),197 .out_buffer = .init(gpa),
194 .stdout_writer = &stdout_writer,198 .stdout_writer = &stdout_writer,
195 .stats = stats,199 .stats = null,
196 .total_tokens = 0,
197 .total_nodes = 0,
198 .serializer = .{ .writer = &stdout_writer.interface },
199 .root_struct = undefined,
200 .token_stats = undefined,
201 .node_stats = undefined,
202 };200 };
203 defer fmt.seen.deinit();201 defer fmt.seen.deinit();
204 defer fmt.out_buffer.deinit();202 defer fmt.out_buffer.deinit();
205203
206 if (stats) {204 if (enable_stats) {
207 fmt.root_struct = try fmt.serializer.beginStruct(.{});205 const stats = try arena.create(Stats);
208 fmt.token_stats = try arena.alloc(u64, @typeInfo(std.zig.Token.Tag).@"enum".field_names.len);206 fmt.stats = stats;
209 fmt.node_stats = try arena.alloc(u64, @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names.len);207 stats.* = .{
208 .files = 0,
209 .tokens = 0,
210 .nodes = 0,
211 .serializer = .{ .writer = &stdout_writer.interface },
212 .root_struct = try stats.serializer.beginStruct(.{}),
213 .token_stats = try arena.alloc(u64, @typeInfo(std.zig.Token.Tag).@"enum".field_names.len),
214 .node_stats = try arena.alloc(u64, @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names.len),
215 };
216 @memset(stats.token_stats, 0);
217 @memset(stats.node_stats, 0);
210 }218 }
211219
212 // Mark any excluded files/directories as already seen,220 // Mark any excluded files/directories as already seen,
...@@ -229,10 +237,13 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !...@@ -229,10 +237,13 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
229 try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path);237 try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path);
230 }238 }
231239
232 if (stats) {240 if (fmt.stats) |stats| {
233 try fmt.root_struct.field("tokens", fmt.total_tokens, .{});241 if (stats.files > 1) {
234 try fmt.root_struct.field("nodes", fmt.total_nodes, .{});242 try stats.root_struct.field("files", stats.files, .{});
235 try fmt.root_struct.end();243 try stats.root_struct.field("tokens", stats.tokens, .{});
244 try stats.root_struct.field("nodes", stats.nodes, .{});
245 }
246 try stats.root_struct.end();
236 }247 }
237248
238 try fmt.stdout_writer.flush();249 try fmt.stdout_writer.flush();
...@@ -378,13 +389,73 @@ fn fmtPathFile(...@@ -378,13 +389,73 @@ fn fmtPathFile(
378389
379 var zon_struct: Serializer.Struct = undefined;390 var zon_struct: Serializer.Struct = undefined;
380391
381 if (fmt.stats) {392 if (fmt.stats) |stats| {
382 zon_struct = try fmt.root_struct.beginStructField(file_path, .{});393 zon_struct = try stats.root_struct.beginStructField(file_path, .{});
383 try zon_struct.field("tokens", tree.tokens.len, .{});394
384 try zon_struct.field("nodes", tree.nodes.len, .{});395 try zon_struct.field("token_count", tree.tokens.len, .{});
396 try zon_struct.field("node_count", tree.nodes.len, .{});
397
398 const token_names = @typeInfo(std.zig.Token.Tag).@"enum".field_names;
399 const node_names = @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names;
400
401 var token_stats: [token_names.len]u64 = @splat(0);
402 var token_order: [token_names.len]usize = undefined;
403 var node_stats: [node_names.len]u64 = @splat(0);
404 var node_order: [node_names.len]usize = undefined;
405
406 for (tree.tokens.items(.tag)) |tag| {
407 stats.token_stats[@backingInt(tag)] += 1;
408 token_stats[@backingInt(tag)] += 1;
409 }
410
411 for (tree.nodes.items(.tag)) |tag| {
412 stats.node_stats[@backingInt(tag)] += 1;
413 node_stats[@backingInt(tag)] += 1;
414 }
415
416 const SortContext = struct {
417 stats: []const u64,
418 order: []usize,
419
420 pub fn lessThan(this: @This(), a_index: usize, b_index: usize) bool {
421 return this.stats[this.order[b_index]] < this.stats[this.order[a_index]];
422 }
423
424 pub fn swap(this: @This(), a_index: usize, b_index: usize) void {
425 std.mem.swap(usize, &this.order[a_index], &this.order[b_index]);
426 }
427 };
428
429 {
430 for (&token_order, 0..) |*elem, i| elem.* = i;
431 std.mem.sortContext(0, token_names.len, @as(SortContext, .{
432 .stats = &token_stats,
433 .order = &token_order,
434 }));
435 var s = try zon_struct.beginStructField("tokens", .{});
436 for (&token_order) |i| {
437 const n = token_stats[i];
438 if (n != 0) try zon_struct.field(token_names[i], n, .{});
439 }
440 try s.end();
441 }
442
443 {
444 for (&node_order, 0..) |*elem, i| elem.* = i;
445 std.mem.sortContext(0, node_names.len, @as(SortContext, .{
446 .stats = &node_stats,
447 .order = &node_order,
448 }));
449 var s = try zon_struct.beginStructField("nodes", .{});
450 for (&node_order) |i| {
451 const n = node_stats[i];
452 if (n != 0) try zon_struct.field(node_names[i], n, .{});
453 }
454 try s.end();
455 }
385456
386 fmt.total_tokens += tree.tokens.len;457 stats.tokens += tree.tokens.len;
387 fmt.total_nodes += tree.nodes.len;458 stats.nodes += tree.nodes.len;
388 }459 }
389460
390 // As a heuristic, we make enough capacity for the same as the input source.461 // As a heuristic, we make enough capacity for the same as the input source.
...@@ -404,14 +475,14 @@ fn fmtPathFile(...@@ -404,14 +475,14 @@ fn fmtPathFile(
404 try af.file.writeStreamingAll(io, fmt.out_buffer.written());475 try af.file.writeStreamingAll(io, fmt.out_buffer.written());
405 try af.replace(io);476 try af.replace(io);
406 }477 }
407 if (fmt.stats) {478 if (fmt.stats != null) {
408 try zon_struct.field("dirty", true, .{});479 try zon_struct.field("dirty", true, .{});
409 } else {480 } else {
410 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});481 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
411 }482 }
412 }483 }
413484
414 if (fmt.stats) try zon_struct.end();485 if (fmt.stats != null) try zon_struct.end();
415}486}
416487
417/// Provided for debugging/testing purposes; unused by the compiler.488/// Provided for debugging/testing purposes; unused by the compiler.