|
2 | 2 | // The prototypes in src/userland.h must match these definitions.
|
3 | 3 |
|
4 | 4 | const std = @import("std");
|
| 5 | +const builtin = @import("builtin"); |
5 | 6 |
|
6 | 7 | // ABI warning
|
7 | 8 | export fn stage2_zen(ptr: *[*]const u8, len: *usize) void {
|
@@ -119,3 +120,277 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
|
119 | 120 | };
|
120 | 121 | return Error.None;
|
121 | 122 | }
|
| 123 | + |
| 124 | +// TODO: just use the actual self-hosted zig fmt. Until the coroutine rewrite, we use a blocking implementation. |
| 125 | +export fn stage2_fmt(argc: c_int, argv: [*]const [*]const u8) c_int { |
| 126 | + if (std.debug.runtime_safety) { |
| 127 | + fmtMain(argc, argv) catch unreachable; |
| 128 | + } else { |
| 129 | + fmtMain(argc, argv) catch |e| { |
| 130 | + std.debug.warn("{}\n", @errorName(e)); |
| 131 | + return -1; |
| 132 | + } |
| 133 | + } |
| 134 | + return 0; |
| 135 | +} |
| 136 | + |
| 137 | +fn fmtMain(argc: c_int, argv: [*]const [*]const u8) !void { |
| 138 | + const allocator = std.heap.c_allocator; |
| 139 | + var args_list = std.ArrayList([]const u8).init(allocator); |
| 140 | + const argc_usize = @intCast(usize, argc); |
| 141 | + var arg_i: usize = 0; |
| 142 | + while (arg_i < argc_usize) : (arg_i += 1) { |
| 143 | + try args_list.append(std.mem.toSliceConst(u8, argv[arg_i])); |
| 144 | + } |
| 145 | + |
| 146 | + var stdout_file = try std.io.getStdOut(); |
| 147 | + var stdout_out_stream = stdout_file.outStream(); |
| 148 | + stdout = &stdout_out_stream.stream; |
| 149 | + |
| 150 | + stderr_file = try std.io.getStdErr(); |
| 151 | + var stderr_out_stream = stderr_file.outStream(); |
| 152 | + stderr = &stderr_out_stream.stream; |
| 153 | + |
| 154 | + const args = args_list.toSliceConst(); |
| 155 | + var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[2..]); |
| 156 | + defer flags.deinit(); |
| 157 | + |
| 158 | + if (flags.present("help")) { |
| 159 | + try stdout.write(self_hosted_main.usage_fmt); |
| 160 | + os.exit(0); |
| 161 | + } |
| 162 | + |
| 163 | + const color = blk: { |
| 164 | + if (flags.single("color")) |color_flag| { |
| 165 | + if (mem.eql(u8, color_flag, "auto")) { |
| 166 | + break :blk errmsg.Color.Auto; |
| 167 | + } else if (mem.eql(u8, color_flag, "on")) { |
| 168 | + break :blk errmsg.Color.On; |
| 169 | + } else if (mem.eql(u8, color_flag, "off")) { |
| 170 | + break :blk errmsg.Color.Off; |
| 171 | + } else unreachable; |
| 172 | + } else { |
| 173 | + break :blk errmsg.Color.Auto; |
| 174 | + } |
| 175 | + }; |
| 176 | + |
| 177 | + if (flags.present("stdin")) { |
| 178 | + if (flags.positionals.len != 0) { |
| 179 | + try stderr.write("cannot use --stdin with positional arguments\n"); |
| 180 | + os.exit(1); |
| 181 | + } |
| 182 | + |
| 183 | + var stdin_file = try io.getStdIn(); |
| 184 | + var stdin = stdin_file.inStream(); |
| 185 | + |
| 186 | + const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size); |
| 187 | + defer allocator.free(source_code); |
| 188 | + |
| 189 | + const tree = std.zig.parse(allocator, source_code) catch |err| { |
| 190 | + try stderr.print("error parsing stdin: {}\n", err); |
| 191 | + os.exit(1); |
| 192 | + }; |
| 193 | + defer tree.deinit(); |
| 194 | + |
| 195 | + var error_it = tree.errors.iterator(0); |
| 196 | + while (error_it.next()) |parse_error| { |
| 197 | + try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color); |
| 198 | + } |
| 199 | + if (tree.errors.len != 0) { |
| 200 | + os.exit(1); |
| 201 | + } |
| 202 | + if (flags.present("check")) { |
| 203 | + const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree); |
| 204 | + const code = if (anything_changed) u8(1) else u8(0); |
| 205 | + os.exit(code); |
| 206 | + } |
| 207 | + |
| 208 | + _ = try std.zig.render(allocator, stdout, tree); |
| 209 | + return; |
| 210 | + } |
| 211 | + |
| 212 | + if (flags.positionals.len == 0) { |
| 213 | + try stderr.write("expected at least one source file argument\n"); |
| 214 | + os.exit(1); |
| 215 | + } |
| 216 | + |
| 217 | + var fmt = Fmt{ |
| 218 | + .seen = Fmt.SeenMap.init(allocator), |
| 219 | + .any_error = false, |
| 220 | + .color = color, |
| 221 | + .allocator = allocator, |
| 222 | + }; |
| 223 | + |
| 224 | + const check_mode = flags.present("check"); |
| 225 | + |
| 226 | + for (flags.positionals.toSliceConst()) |file_path| { |
| 227 | + try fmtPath(&fmt, file_path, check_mode); |
| 228 | + } |
| 229 | + if (fmt.any_error) { |
| 230 | + os.exit(1); |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +const FmtError = error{ |
| 235 | + SystemResources, |
| 236 | + OperationAborted, |
| 237 | + IoPending, |
| 238 | + BrokenPipe, |
| 239 | + Unexpected, |
| 240 | + WouldBlock, |
| 241 | + FileClosed, |
| 242 | + DestinationAddressRequired, |
| 243 | + DiskQuota, |
| 244 | + FileTooBig, |
| 245 | + InputOutput, |
| 246 | + NoSpaceLeft, |
| 247 | + AccessDenied, |
| 248 | + OutOfMemory, |
| 249 | + RenameAcrossMountPoints, |
| 250 | + ReadOnlyFileSystem, |
| 251 | + LinkQuotaExceeded, |
| 252 | + FileBusy, |
| 253 | +} || os.File.OpenError; |
| 254 | + |
| 255 | +fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { |
| 256 | + const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); |
| 257 | + defer fmt.allocator.free(file_path); |
| 258 | + |
| 259 | + if (try fmt.seen.put(file_path, {})) |_| return; |
| 260 | + |
| 261 | + const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { |
| 262 | + error.IsDir, error.AccessDenied => { |
| 263 | + // TODO make event based (and dir.next()) |
| 264 | + var dir = try std.os.Dir.open(fmt.allocator, file_path); |
| 265 | + defer dir.close(); |
| 266 | + |
| 267 | + while (try dir.next()) |entry| { |
| 268 | + if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { |
| 269 | + const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); |
| 270 | + try fmtPath(fmt, full_path, check_mode); |
| 271 | + } |
| 272 | + } |
| 273 | + return; |
| 274 | + }, |
| 275 | + else => { |
| 276 | + // TODO lock stderr printing |
| 277 | + try stderr.print("unable to open '{}': {}\n", file_path, err); |
| 278 | + fmt.any_error = true; |
| 279 | + return; |
| 280 | + }, |
| 281 | + }; |
| 282 | + defer fmt.allocator.free(source_code); |
| 283 | + |
| 284 | + const tree = std.zig.parse(fmt.allocator, source_code) catch |err| { |
| 285 | + try stderr.print("error parsing file '{}': {}\n", file_path, err); |
| 286 | + fmt.any_error = true; |
| 287 | + return; |
| 288 | + }; |
| 289 | + defer tree.deinit(); |
| 290 | + |
| 291 | + var error_it = tree.errors.iterator(0); |
| 292 | + while (error_it.next()) |parse_error| { |
| 293 | + try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color); |
| 294 | + } |
| 295 | + if (tree.errors.len != 0) { |
| 296 | + fmt.any_error = true; |
| 297 | + return; |
| 298 | + } |
| 299 | + |
| 300 | + if (check_mode) { |
| 301 | + const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree); |
| 302 | + if (anything_changed) { |
| 303 | + try stderr.print("{}\n", file_path); |
| 304 | + fmt.any_error = true; |
| 305 | + } |
| 306 | + } else { |
| 307 | + const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); |
| 308 | + defer baf.destroy(); |
| 309 | + |
| 310 | + const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree); |
| 311 | + if (anything_changed) { |
| 312 | + try stderr.print("{}\n", file_path); |
| 313 | + try baf.finish(); |
| 314 | + } |
| 315 | + } |
| 316 | +} |
| 317 | + |
| 318 | +const Fmt = struct { |
| 319 | + seen: SeenMap, |
| 320 | + any_error: bool, |
| 321 | + color: errmsg.Color, |
| 322 | + allocator: *mem.Allocator, |
| 323 | + |
| 324 | + const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); |
| 325 | +}; |
| 326 | + |
| 327 | +fn printErrMsgToFile( |
| 328 | + allocator: *mem.Allocator, |
| 329 | + parse_error: *const ast.Error, |
| 330 | + tree: *ast.Tree, |
| 331 | + path: []const u8, |
| 332 | + file: os.File, |
| 333 | + color: errmsg.Color, |
| 334 | +) !void { |
| 335 | + const color_on = switch (color) { |
| 336 | + errmsg.Color.Auto => file.isTty(), |
| 337 | + errmsg.Color.On => true, |
| 338 | + errmsg.Color.Off => false, |
| 339 | + }; |
| 340 | + const lok_token = parse_error.loc(); |
| 341 | + const span = errmsg.Span{ |
| 342 | + .first = lok_token, |
| 343 | + .last = lok_token, |
| 344 | + }; |
| 345 | + |
| 346 | + const first_token = tree.tokens.at(span.first); |
| 347 | + const last_token = tree.tokens.at(span.last); |
| 348 | + const start_loc = tree.tokenLocationPtr(0, first_token); |
| 349 | + const end_loc = tree.tokenLocationPtr(first_token.end, last_token); |
| 350 | + |
| 351 | + var text_buf = try std.Buffer.initSize(allocator, 0); |
| 352 | + var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; |
| 353 | + try parse_error.render(&tree.tokens, out_stream); |
| 354 | + const text = text_buf.toOwnedSlice(); |
| 355 | + |
| 356 | + const stream = &file.outStream().stream; |
| 357 | + if (!color_on) { |
| 358 | + try stream.print( |
| 359 | + "{}:{}:{}: error: {}\n", |
| 360 | + path, |
| 361 | + start_loc.line + 1, |
| 362 | + start_loc.column + 1, |
| 363 | + text, |
| 364 | + ); |
| 365 | + return; |
| 366 | + } |
| 367 | + |
| 368 | + try stream.print( |
| 369 | + "{}:{}:{}: error: {}\n{}\n", |
| 370 | + path, |
| 371 | + start_loc.line + 1, |
| 372 | + start_loc.column + 1, |
| 373 | + text, |
| 374 | + tree.source[start_loc.line_start..start_loc.line_end], |
| 375 | + ); |
| 376 | + try stream.writeByteNTimes(' ', start_loc.column); |
| 377 | + try stream.writeByteNTimes('~', last_token.end - first_token.start); |
| 378 | + try stream.write("\n"); |
| 379 | +} |
| 380 | + |
| 381 | +const os = std.os; |
| 382 | +const io = std.io; |
| 383 | +const mem = std.mem; |
| 384 | +const Allocator = mem.Allocator; |
| 385 | +const ArrayList = std.ArrayList; |
| 386 | +const Buffer = std.Buffer; |
| 387 | + |
| 388 | +const arg = @import("arg.zig"); |
| 389 | +const self_hosted_main = @import("main.zig"); |
| 390 | +const Args = arg.Args; |
| 391 | +const Flag = arg.Flag; |
| 392 | +const errmsg = @import("errmsg.zig"); |
| 393 | + |
| 394 | +var stderr_file: os.File = undefined; |
| 395 | +var stderr: *io.OutStream(os.File.WriteError) = undefined; |
| 396 | +var stdout: *io.OutStream(os.File.WriteError) = undefined; |
0 commit comments