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 {
4040 io: Io,
4141 out_buffer: std.Io.Writer.Allocating,
4242 stdout_writer: *Io.File.Writer,
43 stats: ?*Stats,
44
45 const SeenMap = std.AutoHashMap(Io.File.INode, void);
46};
47
48const Stats = struct {
4349 serializer: Serializer,
4450 root_struct: Serializer.Struct,
4551
46 stats: bool,
47 total_tokens: u64,
48 total_nodes: u64,
52 files: u32,
53 tokens: u64,
54 nodes: u64,
4955 /// Count per token tag index
5056 token_stats: []u64,
5157 /// Count per node tag index
5258 node_stats: []u64,
53
54 const SeenMap = std.AutoHashMap(Io.File.INode, void);
5559};
5660
5761pub 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) !
6064 var check_flag = false;
6165 var check_ast_flag = false;
6266 var force_zon = false;
63 var stats = false;
67 var enable_stats = false;
6468
6569 var input_files = std.array_list.Managed([]const u8).init(gpa);
6670 defer input_files.deinit();
......@@ -92,7 +96,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
9296 } else if (mem.eql(u8, arg, "--ast-check")) {
9397 check_ast_flag = true;
9498 } else if (mem.eql(u8, arg, "--stats")) {
95 stats = true;
99 enable_stats = true;
96100 } else if (mem.eql(u8, arg, "--exclude")) {
97101 if (i + 1 >= args.len) {
98102 fatal("expected parameter after --exclude", .{});
......@@ -192,21 +196,25 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
192196 .color = color,
193197 .out_buffer = .init(gpa),
194198 .stdout_writer = &stdout_writer,
195 .stats = stats,
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,
199 .stats = null,
202200 };
203201 defer fmt.seen.deinit();
204202 defer fmt.out_buffer.deinit();
205203
206 if (stats) {
207 fmt.root_struct = try fmt.serializer.beginStruct(.{});
208 fmt.token_stats = try arena.alloc(u64, @typeInfo(std.zig.Token.Tag).@"enum".field_names.len);
209 fmt.node_stats = try arena.alloc(u64, @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names.len);
204 if (enable_stats) {
205 const stats = try arena.create(Stats);
206 fmt.stats = stats;
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);
210218 }
211219
212220 // 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) !
229237 try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path);
230238 }
231239
232 if (stats) {
233 try fmt.root_struct.field("tokens", fmt.total_tokens, .{});
234 try fmt.root_struct.field("nodes", fmt.total_nodes, .{});
235 try fmt.root_struct.end();
240 if (fmt.stats) |stats| {
241 if (stats.files > 1) {
242 try stats.root_struct.field("files", stats.files, .{});
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();
236247 }
237248
238249 try fmt.stdout_writer.flush();
......@@ -378,13 +389,73 @@ fn fmtPathFile(
378389
379390 var zon_struct: Serializer.Struct = undefined;
380391
381 if (fmt.stats) {
382 zon_struct = try fmt.root_struct.beginStructField(file_path, .{});
383 try zon_struct.field("tokens", tree.tokens.len, .{});
384 try zon_struct.field("nodes", tree.nodes.len, .{});
392 if (fmt.stats) |stats| {
393 zon_struct = try stats.root_struct.beginStructField(file_path, .{});
394
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;
387 fmt.total_nodes += tree.nodes.len;
457 stats.tokens += tree.tokens.len;
458 stats.nodes += tree.nodes.len;
388459 }
389460
390461 // As a heuristic, we make enough capacity for the same as the input source.
......@@ -404,14 +475,14 @@ fn fmtPathFile(
404475 try af.file.writeStreamingAll(io, fmt.out_buffer.written());
405476 try af.replace(io);
406477 }
407 if (fmt.stats) {
478 if (fmt.stats != null) {
408479 try zon_struct.field("dirty", true, .{});
409480 } else {
410481 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
411482 }
412483 }
413484
414 if (fmt.stats) try zon_struct.end();
485 if (fmt.stats != null) try zon_struct.end();
415486}
416487
417488/// Provided for debugging/testing purposes; unused by the compiler.