Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6c8a8b3

Browse files
committedMar 15, 2025··
Expand roc format to support directories
Also supports formatting multiple passed in args. Also prints the overall execution time.
1 parent 60e0569 commit 6c8a8b3

File tree

4 files changed

+126
-69
lines changed

4 files changed

+126
-69
lines changed
 

‎src/collections/utils.zig

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ pub fn exitOnOom(err: std.mem.Allocator.Error) noreturn {
1111
const oom_message =
1212
\\I ran out of memory! I can't do anything to recover, so I'm exiting.
1313
\\Try reducing memory usage on your machine and then running again.
14+
\\
1415
;
15-
16-
std.debug.print(oom_message, .{});
17-
std.process.exit(1);
16+
fatal(oom_message, .{});
1817
},
1918
}
2019
}
20+
21+
/// Log a fatal error and exit the process with a non-zero code.
22+
pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
23+
std.io.getStdErr().writer().print(format, args) catch unreachable;
24+
std.process.exit(1);
25+
}

‎src/coordinate/Filesystem.zig

+5-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ pub fn default() Self {
3131
};
3232
}
3333

34+
/// The max valid file size.
35+
/// Anything larger will fail due to us using u32 offsets.
36+
pub const max_file_size = std.math.maxInt(u32);
37+
3438
/// All errors that can occur when reading a file.
3539
pub const ReadError = std.fs.File.OpenError || std.posix.ReadError || Allocator.Error || error{StreamTooLong};
3640

@@ -137,8 +141,7 @@ fn readFileDefault(relative_path: []const u8, allocator: std.mem.Allocator) Read
137141
const file = try std.fs.cwd().openFile(relative_path, .{});
138142
defer file.close();
139143

140-
const max_allowed_file_length = std.math.maxInt(usize);
141-
const contents = try file.reader().readAllAlloc(allocator, max_allowed_file_length);
144+
const contents = try file.reader().readAllAlloc(allocator, max_file_size);
142145

143146
return contents;
144147
}

‎src/fmt.zig

+100-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Formatting logic for Roc modules.
22

33
const std = @import("std");
4+
const parse = @import("check/parse.zig").parse;
45
const IR = @import("check/parse/IR.zig");
56
const Node = IR.Node;
7+
const Filesystem = @import("coordinate/Filesystem.zig");
68
const tokenizer = @import("check/parse/tokenize.zig");
79
const TokenizedBuffer = tokenizer.TokenizedBuffer;
810
const TokenIdx = tokenizer.Token.Idx;
911
const exitOnOom = @import("./collections/utils.zig").exitOnOom;
12+
const fatal = @import("./collections/utils.zig").fatal;
1013
const base = @import("base.zig");
1114

1215
const NodeStore = IR.NodeStore;
@@ -42,6 +45,75 @@ pub fn resetWith(fmt: *Formatter, ast: IR) void {
4245
fmt.ast = ast;
4346
}
4447

48+
/// Formats all roc files in the specified path.
49+
/// Handles both single files and directories
50+
/// Returns the number of files formatted.
51+
pub fn formatPath(gpa: std.mem.Allocator, base_dir: std.fs.Dir, path: []const u8) !usize {
52+
// TODO: update this to use the filesystem abstraction
53+
// When doing so, add a mock filesystem and some tests.
54+
var count: usize = 0;
55+
// First try as a directory.
56+
if (base_dir.openDir(path, .{ .iterate = true })) |const_dir| {
57+
var dir = const_dir;
58+
defer dir.close();
59+
// Walk is recursive.
60+
var walker = try dir.walk(gpa);
61+
defer walker.deinit();
62+
while (try walker.next()) |entry| {
63+
if (entry.kind == .file) {
64+
if (try formatFilePath(gpa, entry.dir, entry.basename)) {
65+
count += 1;
66+
}
67+
}
68+
}
69+
} else |_| {
70+
if (try formatFilePath(gpa, base_dir, path)) {
71+
count += 1;
72+
}
73+
}
74+
75+
return count;
76+
}
77+
78+
fn formatFilePath(gpa: std.mem.Allocator, base_dir: std.fs.Dir, path: []const u8) !bool {
79+
// Format any ".roc" files.
80+
if (std.mem.eql(u8, std.fs.path.extension(path), ".roc")) {
81+
const input_file = try base_dir.openFile(path, .{ .mode = .read_only });
82+
defer input_file.close();
83+
84+
const contents = try input_file.reader().readAllAlloc(gpa, Filesystem.max_file_size);
85+
defer gpa.free(contents);
86+
87+
var module_env = base.ModuleEnv.init(gpa);
88+
defer module_env.deinit();
89+
90+
var parse_ast = parse(&module_env, contents);
91+
defer parse_ast.deinit();
92+
if (parse_ast.errors.len > 0) {
93+
// TODO: pretty print the parse failures.
94+
const stderr = std.io.getStdErr().writer();
95+
try stderr.print("Failed to parse '{s}' for formatting.\nErrors:\n", .{path});
96+
for (parse_ast.errors) |err| {
97+
try stderr.print("\t{s}\n", .{@tagName(err.tag)});
98+
}
99+
fatal("\n", .{});
100+
}
101+
102+
var formatter = init(parse_ast);
103+
defer formatter.deinit();
104+
105+
const formatted_output = formatter.formatFile();
106+
defer gpa.free(formatted_output);
107+
108+
const output_file = try base_dir.createFile(path, .{});
109+
defer output_file.close();
110+
try output_file.writeAll(formatted_output);
111+
return true;
112+
}
113+
114+
return false;
115+
}
116+
45117
/// Emits a string containing the well-formed source of a Roc parse IR (AST).
46118
/// The resulting string is owned by the caller.
47119
pub fn formatFile(fmt: *Formatter) []const u8 {
@@ -728,8 +800,6 @@ fn pushTokenText(fmt: *Formatter, ti: TokenIdx) void {
728800
}
729801

730802
fn moduleFmtsSame(source: []const u8) !void {
731-
const parse = @import("check/parse.zig").parse;
732-
733803
const gpa = std.testing.allocator;
734804

735805
var env = base.ModuleEnv.init(gpa);
@@ -836,8 +906,6 @@ pub fn moduleFmtsStable(gpa: std.mem.Allocator, input: []const u8, debug: bool)
836906
}
837907

838908
fn parseAndFmt(gpa: std.mem.Allocator, input: []const u8, debug: bool) ![]const u8 {
839-
const parse = @import("check/parse.zig").parse;
840-
841909
var module_env = base.ModuleEnv.init(gpa);
842910
defer module_env.deinit();
843911

@@ -1064,3 +1132,31 @@ test "Dot access super test" {
10641132
const expr = "some_fn(arg1)?.static_dispatch_method()?.next_static_dispatch_method()?.record_field?";
10651133
try exprFmtsSame(expr, .no_debug);
10661134
}
1135+
1136+
// TODO: replace this test with one that doesn't interact with the real filesystem.
1137+
test "format single file" {
1138+
const gpa = std.testing.allocator;
1139+
const roc_filename = "test.roc";
1140+
1141+
const roc_file = try std.fs.cwd().createFile(roc_filename, .{ .read = true });
1142+
defer roc_file.close();
1143+
try roc_file.writeAll(
1144+
\\module []
1145+
\\
1146+
\\foo = "bar"
1147+
);
1148+
defer std.fs.cwd().deleteFile(roc_filename) catch std.debug.panic("Failed to clean up test.roc", .{});
1149+
1150+
try formatPath(gpa, std.fs.cwd(), roc_filename);
1151+
1152+
// Reset file position to read formatted roc code
1153+
try roc_file.seekTo(0);
1154+
const formatted_code = try roc_file.reader().readAllAlloc(gpa, Filesystem.max_file_size);
1155+
defer gpa.free(formatted_code);
1156+
1157+
try std.testing.expectEqualStrings(
1158+
\\module []
1159+
\\
1160+
\\foo = "bar"
1161+
, formatted_code);
1162+
}

‎src/main.zig

+13-60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const std = @import("std");
22
const fmt = @import("fmt.zig");
3-
const parse = @import("check/parse.zig");
43
const base = @import("base.zig");
54
const cli = @import("cli.zig");
65
const collections = @import("collections.zig");
@@ -13,6 +12,7 @@ const RocOpt = cli.RocOpt;
1312
const Problem = problem_mod.Problem;
1413
const Allocator = std.mem.Allocator;
1514
const exitOnOom = collections.utils.exitOnOom;
15+
const fatal = collections.utils.fatal;
1616

1717
const usage =
1818
\\Usage:
@@ -36,12 +36,6 @@ const usage =
3636
\\ -h, --help Print usage
3737
;
3838

39-
/// Log a fatal error and exit the process with a non-zero code.
40-
pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
41-
std.io.getStdErr().writer().print(format, args) catch unreachable;
42-
std.process.exit(1);
43-
}
44-
4539
/// The CLI entrypoint for the Roc compiler.
4640
pub fn main() !void {
4741
const gpa = std.heap.c_allocator;
@@ -125,62 +119,21 @@ fn rocRepl(gpa: Allocator, opt: RocOpt, args: []const []const u8) !void {
125119
fatal("not implemented", .{});
126120
}
127121

128-
/// Reads, parses, formats, and overwrites a Roc file.
122+
/// Reads, parses, formats, and overwrites all Roc files at the given paths.
123+
/// Recurses into directories to search for Roc files.
129124
fn rocFormat(gpa: Allocator, args: []const []const u8) !void {
130-
const roc_file_path = if (args.len > 0) args[0] else "main.roc";
131-
132-
const input_file = try std.fs.cwd().openFile(roc_file_path, .{ .mode = .read_only });
133-
defer input_file.close();
134-
135-
const contents = try input_file.reader().readAllAlloc(gpa, std.math.maxInt(usize));
136-
defer gpa.free(contents);
137-
138-
var module_env = base.ModuleEnv.init(gpa);
139-
defer module_env.deinit();
140-
141-
var parse_ast = parse.parse(&module_env, contents);
142-
defer parse_ast.deinit();
143-
if (parse_ast.errors.len > 0) {
144-
// TODO: pretty print the parse failures.
145-
fatal("Failed to parse '{s}' for formatting.\nErrors:\n{any}\n", .{ roc_file_path, parse_ast.errors });
125+
// var timer = try std.time.Timer.start();
126+
var count: usize = 0;
127+
if (args.len > 0) {
128+
for (args) |arg| {
129+
count += try fmt.formatPath(gpa, std.fs.cwd(), arg);
130+
}
131+
} else {
132+
count = try fmt.formatPath(gpa, std.fs.cwd(), "main.roc");
146133
}
147134

148-
var formatter = fmt.init(parse_ast);
149-
defer formatter.deinit();
150-
151-
const formatted_output = formatter.formatFile();
152-
defer gpa.free(formatted_output);
153-
154-
const output_file = try std.fs.cwd().createFile(roc_file_path, .{});
155-
defer output_file.close();
156-
try output_file.writeAll(formatted_output);
157-
}
158-
159-
test "format single file" {
160-
const gpa = std.testing.allocator;
161-
const roc_filename = "test.roc";
162-
163-
const roc_file = try std.fs.cwd().createFile(roc_filename, .{ .read = true });
164-
defer roc_file.close();
165-
try roc_file.writeAll(
166-
\\module []
167-
\\
168-
\\foo = "bar"
169-
);
170-
defer std.fs.cwd().deleteFile(roc_filename) catch std.debug.panic("Failed to clean up test.roc", .{});
171-
172-
try rocFormat(gpa, &.{roc_filename});
173-
174-
// Reset file position to read formatted roc code
175-
try roc_file.seekTo(0);
176-
const formatted_code = try roc_file.reader().readAllAlloc(gpa, std.math.maxInt(usize));
177-
defer gpa.free(formatted_code);
178-
179-
try std.testing.expectEqualStrings(
180-
\\module []
181-
\\
182-
\\foo = "bar"
183-
, formatted_code);
135+
// const elapsed = timer.read() / std.time.ns_per_ms;
136+
// try std.io.getStdOut().writer().print("Successfully formatted {} files in {} ms.\n", .{ count, elapsed });
184137
}
185138

186139
fn rocVersion(gpa: Allocator, args: []const []const u8) !void {

0 commit comments

Comments
 (0)
Please sign in to comment.