| ... | ... | @@ -6,6 +6,7 @@ const process = std.process; |
| 6 | 6 | const Allocator = std.mem.Allocator; |
| 7 | 7 | const Color = std.zig.Color; |
| 8 | 8 | const fatal = std.process.fatal; |
| 9 | const Serializer = std.zon.Serializer; |
| 9 | 10 | |
| 10 | 11 | const usage_fmt = |
| 11 | 12 | \\Usage: zig fmt [file]... |
| ... | ... | @@ -23,7 +24,7 @@ const usage_fmt = |
| 23 | 24 | \\ --ast-check Run zig ast-check on every file |
| 24 | 25 | \\ --exclude [file] Exclude file or directory from formatting |
| 25 | 26 | \\ --zon Treat all input files as ZON, regardless of file extension |
| 26 | | \\ --complexity Print a complexity report for each file as well as total |
| 27 | \\ --stats Print aggregated statistics in ZON format to stdout |
| 27 | 28 | \\ |
| 28 | 29 | \\ |
| 29 | 30 | ; |
| ... | ... | @@ -39,21 +40,32 @@ const Fmt = struct { |
| 39 | 40 | io: Io, |
| 40 | 41 | out_buffer: std.Io.Writer.Allocating, |
| 41 | 42 | stdout_writer: *Io.File.Writer, |
| 42 | | |
| 43 | | complexity: bool, |
| 44 | | total_tokens: u64, |
| 45 | | total_nodes: u64, |
| 43 | stats: ?*Stats, |
| 46 | 44 | |
| 47 | 45 | const SeenMap = std.AutoHashMap(Io.File.INode, void); |
| 48 | 46 | }; |
| 49 | 47 | |
| 48 | const Stats = struct { |
| 49 | serializer: Serializer, |
| 50 | root_struct: Serializer.Struct, |
| 51 | |
| 52 | files: u32, |
| 53 | tokens: u64, |
| 54 | nodes: u64, |
| 55 | source_size: u64, |
| 56 | /// Count per token tag index |
| 57 | token_stats: []u64, |
| 58 | /// Count per node tag index |
| 59 | node_stats: []u64, |
| 60 | }; |
| 61 | |
| 50 | 62 | pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !void { |
| 51 | 63 | var color: Color = .auto; |
| 52 | 64 | var stdin_flag = false; |
| 53 | 65 | var check_flag = false; |
| 54 | 66 | var check_ast_flag = false; |
| 55 | 67 | var force_zon = false; |
| 56 | | var complexity = false; |
| 68 | var enable_stats = false; |
| 57 | 69 | |
| 58 | 70 | var input_files = std.array_list.Managed([]const u8).init(gpa); |
| 59 | 71 | defer input_files.deinit(); |
| ... | ... | @@ -84,8 +96,8 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! |
| 84 | 96 | check_flag = true; |
| 85 | 97 | } else if (mem.eql(u8, arg, "--ast-check")) { |
| 86 | 98 | check_ast_flag = true; |
| 87 | | } else if (mem.eql(u8, arg, "--complexity")) { |
| 88 | | complexity = true; |
| 99 | } else if (mem.eql(u8, arg, "--stats")) { |
| 100 | enable_stats = true; |
| 89 | 101 | } else if (mem.eql(u8, arg, "--exclude")) { |
| 90 | 102 | if (i + 1 >= args.len) { |
| 91 | 103 | fatal("expected parameter after --exclude", .{}); |
| ... | ... | @@ -185,13 +197,28 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! |
| 185 | 197 | .color = color, |
| 186 | 198 | .out_buffer = .init(gpa), |
| 187 | 199 | .stdout_writer = &stdout_writer, |
| 188 | | .complexity = complexity, |
| 189 | | .total_tokens = 0, |
| 190 | | .total_nodes = 0, |
| 200 | .stats = null, |
| 191 | 201 | }; |
| 192 | 202 | defer fmt.seen.deinit(); |
| 193 | 203 | defer fmt.out_buffer.deinit(); |
| 194 | 204 | |
| 205 | if (enable_stats) { |
| 206 | const stats = try arena.create(Stats); |
| 207 | fmt.stats = stats; |
| 208 | stats.* = .{ |
| 209 | .files = 0, |
| 210 | .tokens = 0, |
| 211 | .nodes = 0, |
| 212 | .source_size = 0, |
| 213 | .serializer = .{ .writer = &stdout_writer.interface }, |
| 214 | .root_struct = try stats.serializer.beginStruct(.{}), |
| 215 | .token_stats = try arena.alloc(u64, @typeInfo(std.zig.Token.Tag).@"enum".field_names.len), |
| 216 | .node_stats = try arena.alloc(u64, @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names.len), |
| 217 | }; |
| 218 | @memset(stats.token_stats, 0); |
| 219 | @memset(stats.node_stats, 0); |
| 220 | } |
| 221 | |
| 195 | 222 | // Mark any excluded files/directories as already seen, |
| 196 | 223 | // so that they are skipped later during actual processing |
| 197 | 224 | for (excluded_files.items) |file_path| { |
| ... | ... | @@ -212,8 +239,50 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! |
| 212 | 239 | try fmtPath(&fmt, file_path, check_flag, Io.Dir.cwd(), file_path); |
| 213 | 240 | } |
| 214 | 241 | |
| 215 | | if (complexity) { |
| 216 | | std.log.info("total: tokens={d} nodes={d}", .{ fmt.total_tokens, fmt.total_nodes }); |
| 242 | if (fmt.stats) |stats| { |
| 243 | if (stats.files > 1) { |
| 244 | var agg_struct = try stats.root_struct.beginStructField("aggregate", .{}); |
| 245 | try agg_struct.field("file_count", stats.files, .{}); |
| 246 | try agg_struct.field("token_count", stats.tokens, .{}); |
| 247 | try agg_struct.field("node_count", stats.nodes, .{}); |
| 248 | try agg_struct.field("source_size", stats.source_size, .{}); |
| 249 | |
| 250 | const token_names = @typeInfo(std.zig.Token.Tag).@"enum".field_names; |
| 251 | const node_names = @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names; |
| 252 | |
| 253 | var token_order: [token_names.len]usize = undefined; |
| 254 | var node_order: [node_names.len]usize = undefined; |
| 255 | { |
| 256 | for (&token_order, 0..) |*elem, i| elem.* = i; |
| 257 | std.mem.sortContext(0, token_names.len, @as(SortContext, .{ |
| 258 | .stats = stats.token_stats, |
| 259 | .order = &token_order, |
| 260 | })); |
| 261 | var tokens_struct = try agg_struct.beginStructField("tokens", .{}); |
| 262 | for (&token_order) |i| { |
| 263 | const n = stats.token_stats[i]; |
| 264 | if (n != 0) try tokens_struct.field(token_names[i], n, .{}); |
| 265 | } |
| 266 | try tokens_struct.end(); |
| 267 | } |
| 268 | { |
| 269 | for (&node_order, 0..) |*elem, i| elem.* = i; |
| 270 | std.mem.sortContext(0, node_names.len, @as(SortContext, .{ |
| 271 | .stats = stats.node_stats, |
| 272 | .order = &node_order, |
| 273 | })); |
| 274 | var nodes_struct = try agg_struct.beginStructField("nodes", .{}); |
| 275 | for (&node_order) |i| { |
| 276 | const n = stats.node_stats[i]; |
| 277 | if (n != 0) try nodes_struct.field(node_names[i], n, .{}); |
| 278 | } |
| 279 | try nodes_struct.end(); |
| 280 | } |
| 281 | |
| 282 | try agg_struct.end(); |
| 283 | } |
| 284 | try stats.root_struct.end(); |
| 285 | try fmt.stdout_writer.interface.writeByte('\n'); |
| 217 | 286 | } |
| 218 | 287 | |
| 219 | 288 | try fmt.stdout_writer.flush(); |
| ... | ... | @@ -357,10 +426,65 @@ fn fmtPathFile( |
| 357 | 426 | } |
| 358 | 427 | } |
| 359 | 428 | |
| 360 | | if (fmt.complexity) { |
| 361 | | std.log.info("{s}: tokens={d} nodes={d}", .{ file_path, tree.tokens.len, tree.nodes.len }); |
| 362 | | fmt.total_tokens += tree.tokens.len; |
| 363 | | fmt.total_nodes += tree.nodes.len; |
| 429 | var zon_struct: Serializer.Struct = undefined; |
| 430 | |
| 431 | if (fmt.stats) |stats| { |
| 432 | zon_struct = try stats.root_struct.beginStructField(file_path, .{}); |
| 433 | |
| 434 | try zon_struct.field("token_count", tree.tokens.len, .{}); |
| 435 | try zon_struct.field("node_count", tree.nodes.len, .{}); |
| 436 | try zon_struct.field("source_size", stat.size, .{}); |
| 437 | |
| 438 | const token_names = @typeInfo(std.zig.Token.Tag).@"enum".field_names; |
| 439 | const node_names = @typeInfo(std.zig.Ast.Node.Tag).@"enum".field_names; |
| 440 | |
| 441 | var token_stats: [token_names.len]u64 = @splat(0); |
| 442 | var token_order: [token_names.len]usize = undefined; |
| 443 | var node_stats: [node_names.len]u64 = @splat(0); |
| 444 | var node_order: [node_names.len]usize = undefined; |
| 445 | |
| 446 | for (tree.tokens.items(.tag)) |tag| { |
| 447 | stats.token_stats[@backingInt(tag)] += 1; |
| 448 | token_stats[@backingInt(tag)] += 1; |
| 449 | } |
| 450 | |
| 451 | for (tree.nodes.items(.tag)) |tag| { |
| 452 | stats.node_stats[@backingInt(tag)] += 1; |
| 453 | node_stats[@backingInt(tag)] += 1; |
| 454 | } |
| 455 | |
| 456 | { |
| 457 | for (&token_order, 0..) |*elem, i| elem.* = i; |
| 458 | std.mem.sortContext(0, token_names.len, @as(SortContext, .{ |
| 459 | .stats = &token_stats, |
| 460 | .order = &token_order, |
| 461 | })); |
| 462 | var s = try zon_struct.beginStructField("tokens", .{}); |
| 463 | for (&token_order) |i| { |
| 464 | const n = token_stats[i]; |
| 465 | if (n != 0) try s.field(token_names[i], n, .{}); |
| 466 | } |
| 467 | try s.end(); |
| 468 | } |
| 469 | |
| 470 | { |
| 471 | for (&node_order, 0..) |*elem, i| elem.* = i; |
| 472 | std.mem.sortContext(0, node_names.len, @as(SortContext, .{ |
| 473 | .stats = &node_stats, |
| 474 | .order = &node_order, |
| 475 | })); |
| 476 | var s = try zon_struct.beginStructField("nodes", .{}); |
| 477 | for (&node_order) |i| { |
| 478 | const n = node_stats[i]; |
| 479 | if (n != 0) try s.field(node_names[i], n, .{}); |
| 480 | } |
| 481 | try s.end(); |
| 482 | } |
| 483 | |
| 484 | stats.tokens += tree.tokens.len; |
| 485 | stats.nodes += tree.nodes.len; |
| 486 | stats.files += 1; |
| 487 | stats.source_size += stat.size; |
| 364 | 488 | } |
| 365 | 489 | |
| 366 | 490 | // As a heuristic, we make enough capacity for the same as the input source. |
| ... | ... | @@ -370,22 +494,39 @@ fn fmtPathFile( |
| 370 | 494 | tree.render(gpa, &fmt.out_buffer.writer, .{}) catch |err| switch (err) { |
| 371 | 495 | error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, |
| 372 | 496 | }; |
| 373 | | if (mem.eql(u8, fmt.out_buffer.written(), source_code)) |
| 374 | | return; |
| 375 | | |
| 376 | | if (check_mode) { |
| 377 | | try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); |
| 378 | | fmt.any_error = true; |
| 379 | | } else { |
| 380 | | var af = try dir.createFileAtomic(io, sub_path, .{ .permissions = stat.permissions, .replace = true }); |
| 381 | | defer af.deinit(io); |
| 497 | if (!mem.eql(u8, fmt.out_buffer.written(), source_code)) { |
| 498 | if (check_mode) { |
| 499 | fmt.any_error = true; |
| 500 | } else { |
| 501 | var af = try dir.createFileAtomic(io, sub_path, .{ .permissions = stat.permissions, .replace = true }); |
| 502 | defer af.deinit(io); |
| 382 | 503 | |
| 383 | | try af.file.writeStreamingAll(io, fmt.out_buffer.written()); |
| 384 | | try af.replace(io); |
| 385 | | try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); |
| 504 | try af.file.writeStreamingAll(io, fmt.out_buffer.written()); |
| 505 | try af.replace(io); |
| 506 | } |
| 507 | if (fmt.stats != null) { |
| 508 | try zon_struct.field("dirty", true, .{}); |
| 509 | } else { |
| 510 | try fmt.stdout_writer.interface.print("{s}\n", .{file_path}); |
| 511 | } |
| 386 | 512 | } |
| 513 | |
| 514 | if (fmt.stats != null) try zon_struct.end(); |
| 387 | 515 | } |
| 388 | 516 | |
| 517 | const SortContext = struct { |
| 518 | stats: []const u64, |
| 519 | order: []usize, |
| 520 | |
| 521 | pub fn lessThan(this: @This(), a_index: usize, b_index: usize) bool { |
| 522 | return this.stats[this.order[b_index]] < this.stats[this.order[a_index]]; |
| 523 | } |
| 524 | |
| 525 | pub fn swap(this: @This(), a_index: usize, b_index: usize) void { |
| 526 | std.mem.swap(usize, &this.order[a_index], &this.order[b_index]); |
| 527 | } |
| 528 | }; |
| 529 | |
| 389 | 530 | /// Provided for debugging/testing purposes; unused by the compiler. |
| 390 | 531 | pub fn main(init: process.Init) !void { |
| 391 | 532 | const args = try init.minimal.args.toSlice(init.arena.allocator()); |