Skip to content

Commit 68dfd00

Browse files
committed
Expand roc format to support directories
Also supports formatting multiple passed in args. Also prints the overall execution time.
1 parent 60e0569 commit 68dfd00

File tree

4 files changed

+127
-69
lines changed

4 files changed

+127
-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

+101-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+
// Skip non ".roc" files.
80+
if (!std.mem.eql(u8, std.fs.path.extension(path), ".roc")) {
81+
return false;
82+
}
83+
84+
const input_file = try base_dir.openFile(path, .{ .mode = .read_only });
85+
defer input_file.close();
86+
87+
const contents = try input_file.reader().readAllAlloc(gpa, Filesystem.max_file_size);
88+
defer gpa.free(contents);
89+
90+
var module_env = base.ModuleEnv.init(gpa);
91+
defer module_env.deinit();
92+
93+
var parse_ast = parse(&module_env, contents);
94+
defer parse_ast.deinit();
95+
if (parse_ast.errors.len > 0) {
96+
// TODO: pretty print the parse failures.
97+
const stderr = std.io.getStdErr().writer();
98+
try stderr.print("Failed to parse '{s}' for formatting.\nErrors:\n", .{path});
99+
for (parse_ast.errors) |err| {
100+
try stderr.print("\t{s}\n", .{@tagName(err.tag)});
101+
}
102+
fatal("\n", .{});
103+
}
104+
105+
var formatter = init(parse_ast);
106+
defer formatter.deinit();
107+
108+
const formatted_output = formatter.formatFile();
109+
defer gpa.free(formatted_output);
110+
111+
const output_file = try base_dir.createFile(path, .{});
112+
defer output_file.close();
113+
try output_file.writeAll(formatted_output);
114+
return true;
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,32 @@ 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+
const count = try formatPath(gpa, std.fs.cwd(), roc_filename);
1151+
try std.testing.expectEqual(1, count);
1152+
1153+
// Reset file position to read formatted roc code
1154+
try roc_file.seekTo(0);
1155+
const formatted_code = try roc_file.reader().readAllAlloc(gpa, Filesystem.max_file_size);
1156+
defer gpa.free(formatted_code);
1157+
1158+
try std.testing.expectEqualStrings(
1159+
\\module []
1160+
\\
1161+
\\foo = "bar"
1162+
, formatted_code);
1163+
}

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)