authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-17 12:06:40+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-07-17 12:06:40+02:00
logc82403020d82718df7d42c9438f3145a9a050640
treea45c8c0e5232f5b016f62d298b38342b22e6e075
parent9af076615e4078c98cb456b82a0dc081f70bc20c
parent741569d5a70026ada754d0f2809a40d00c9a41ca
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24472 from ziglang/zig-fmt

zig fmt: update related functionality to new I/O API

19 files changed, 4092 insertions(+), 4018 deletions(-)

CMakeLists.txt+1-3
...@@ -446,7 +446,6 @@ set(ZIG_STAGE2_SOURCES...@@ -446,7 +446,6 @@ set(ZIG_STAGE2_SOURCES
446 lib/std/dwarf/OP.zig446 lib/std/dwarf/OP.zig
447 lib/std/dwarf/TAG.zig447 lib/std/dwarf/TAG.zig
448 lib/std/elf.zig448 lib/std/elf.zig
449 lib/std/fifo.zig
450 lib/std/fmt.zig449 lib/std/fmt.zig
451 lib/std/fmt/parse_float.zig450 lib/std/fmt/parse_float.zig
452 lib/std/fs.zig451 lib/std/fs.zig
...@@ -462,7 +461,6 @@ set(ZIG_STAGE2_SOURCES...@@ -462,7 +461,6 @@ set(ZIG_STAGE2_SOURCES
462 lib/std/heap.zig461 lib/std/heap.zig
463 lib/std/heap/arena_allocator.zig462 lib/std/heap/arena_allocator.zig
464 lib/std/json.zig463 lib/std/json.zig
465 lib/std/json/stringify.zig
466 lib/std/leb128.zig464 lib/std/leb128.zig
467 lib/std/log.zig465 lib/std/log.zig
468 lib/std/macho.zig466 lib/std/macho.zig
...@@ -505,6 +503,7 @@ set(ZIG_STAGE2_SOURCES...@@ -505,6 +503,7 @@ set(ZIG_STAGE2_SOURCES
505 lib/std/unicode.zig503 lib/std/unicode.zig
506 lib/std/zig.zig504 lib/std/zig.zig
507 lib/std/zig/Ast.zig505 lib/std/zig/Ast.zig
506 lib/std/zig/Ast/Render.zig
508 lib/std/zig/AstGen.zig507 lib/std/zig/AstGen.zig
509 lib/std/zig/AstRlAnnotate.zig508 lib/std/zig/AstRlAnnotate.zig
510 lib/std/zig/LibCInstallation.zig509 lib/std/zig/LibCInstallation.zig
...@@ -513,7 +512,6 @@ set(ZIG_STAGE2_SOURCES...@@ -513,7 +512,6 @@ set(ZIG_STAGE2_SOURCES
513 lib/std/zig/WindowsSdk.zig512 lib/std/zig/WindowsSdk.zig
514 lib/std/zig/Zir.zig513 lib/std/zig/Zir.zig
515 lib/std/zig/c_builtins.zig514 lib/std/zig/c_builtins.zig
516 lib/std/zig/render.zig
517 lib/std/zig/string_literal.zig515 lib/std/zig/string_literal.zig
518 lib/std/zig/system.zig516 lib/std/zig/system.zig
519 lib/std/zig/system/NativePaths.zig517 lib/std/zig/system/NativePaths.zig
lib/compiler/aro_translate_c.zig+1-1
...@@ -1824,7 +1824,7 @@ pub fn main() !void {...@@ -1824,7 +1824,7 @@ pub fn main() !void {
1824 };1824 };
1825 defer tree.deinit(gpa);1825 defer tree.deinit(gpa);
18261826
1827 const formatted = try tree.render(arena);1827 const formatted = try tree.renderAlloc(arena);
1828 try std.fs.File.stdout().writeAll(formatted);1828 try std.fs.File.stdout().writeAll(formatted);
1829 return std.process.cleanExit();1829 return std.process.cleanExit();
1830}1830}
lib/docs/wasm/Walk.zig+8-4
...@@ -433,14 +433,18 @@ fn parse(file_name: []const u8, source: []u8) Oom!Ast {...@@ -433,14 +433,18 @@ fn parse(file_name: []const u8, source: []u8) Oom!Ast {
433 defer ast.deinit(gpa);433 defer ast.deinit(gpa);
434434
435 const token_offsets = ast.tokens.items(.start);435 const token_offsets = ast.tokens.items(.start);
436 var rendered_err: std.ArrayListUnmanaged(u8) = .{};436 var rendered_err: std.Io.Writer.Allocating = .init(gpa);
437 defer rendered_err.deinit(gpa);437 defer rendered_err.deinit();
438 for (ast.errors) |err| {438 for (ast.errors) |err| {
439 const err_offset = token_offsets[err.token] + ast.errorOffset(err);439 const err_offset = token_offsets[err.token] + ast.errorOffset(err);
440 const err_loc = std.zig.findLineColumn(ast.source, err_offset);440 const err_loc = std.zig.findLineColumn(ast.source, err_offset);
441 rendered_err.clearRetainingCapacity();441 rendered_err.clearRetainingCapacity();
442 try ast.renderError(err, rendered_err.writer(gpa));442 ast.renderError(err, &rendered_err.writer) catch |e| switch (e) {
443 log.err("{s}:{d}:{d}: {s}", .{ file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.items });443 error.WriteFailed => return error.OutOfMemory,
444 };
445 log.err("{s}:{d}:{d}: {s}", .{
446 file_name, err_loc.line + 1, err_loc.column + 1, rendered_err.getWritten(),
447 });
444 }448 }
445 return Ast.parse(gpa, "", .zig);449 return Ast.parse(gpa, "", .zig);
446 }450 }
lib/std/Io/Reader.zig+83-70
...@@ -246,33 +246,40 @@ pub fn appendRemaining(...@@ -246,33 +246,40 @@ pub fn appendRemaining(
246 limit: Limit,246 limit: Limit,
247) LimitedAllocError!void {247) LimitedAllocError!void {
248 assert(r.buffer.len != 0); // Needed to detect limit exceeded without losing data.248 assert(r.buffer.len != 0); // Needed to detect limit exceeded without losing data.
249 const buffer = r.buffer;249 const buffer_contents = r.buffer[r.seek..r.end];
250 const buffer_contents = buffer[r.seek..r.end];
251 const copy_len = limit.minInt(buffer_contents.len);250 const copy_len = limit.minInt(buffer_contents.len);
252 try list.ensureUnusedCapacity(gpa, copy_len);251 try list.appendSlice(gpa, r.buffer[0..copy_len]);
253 @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]);
254 list.items.len += copy_len;
255 r.seek += copy_len;252 r.seek += copy_len;
256 if (copy_len == buffer_contents.len) {253 if (buffer_contents.len - copy_len != 0) return error.StreamTooLong;
257 r.seek = 0;254 r.seek = 0;
258 r.end = 0;255 r.end = 0;
259 }256 var remaining = @intFromEnum(limit) - copy_len;
260 var remaining = limit.subtract(copy_len).?;
261 while (true) {257 while (true) {
262 try list.ensureUnusedCapacity(gpa, 1);258 try list.ensureUnusedCapacity(gpa, 1);
263 const dest = remaining.slice(list.unusedCapacitySlice());259 const cap = list.unusedCapacitySlice();
264 const additional_buffer: []u8 = if (@intFromEnum(remaining) == dest.len) buffer else &.{};260 const dest = cap[0..@min(cap.len, remaining)];
265 const n = readVec(r, &.{ dest, additional_buffer }) catch |err| switch (err) {261 if (remaining - dest.len == 0) {
266 error.EndOfStream => break,262 // Additionally provides `buffer` to detect end.
267 error.ReadFailed => return error.ReadFailed,263 const new_remaining = readVecInner(r, &.{}, dest, remaining) catch |err| switch (err) {
268 };264 error.EndOfStream => {
269 if (n > dest.len) {265 if (r.bufferedLen() != 0) return error.StreamTooLong;
270 r.end = n - dest.len;266 return;
271 list.items.len += dest.len;267 },
272 return error.StreamTooLong;268 error.ReadFailed => return error.ReadFailed,
269 };
270 list.items.len += remaining - new_remaining;
271 remaining = new_remaining;
272 } else {
273 // Leave `buffer` empty, appending directly to `list`.
274 var dest_w: Writer = .fixed(dest);
275 const n = r.vtable.stream(r, &dest_w, .limited(dest.len)) catch |err| switch (err) {
276 error.WriteFailed => unreachable, // Prevented by the limit.
277 error.EndOfStream => return,
278 error.ReadFailed => return error.ReadFailed,
279 };
280 list.items.len += n;
281 remaining -= n;
273 }282 }
274 list.items.len += n;
275 remaining = remaining.subtract(n).?;
276 }283 }
277}284}
278285
...@@ -313,60 +320,66 @@ pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize {...@@ -313,60 +320,66 @@ pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize {
313 // buffer capacity requirements met.320 // buffer capacity requirements met.
314 r.seek = 0;321 r.seek = 0;
315 r.end = 0;322 r.end = 0;
316 const first = buf[copy_len..];323 remaining = try readVecInner(r, data[i + 1 ..], buf[copy_len..], remaining);
317 const middle = data[i + 1 ..];324 break;
318 var wrapper: Writer.VectorWrapper = .{325 }
319 .it = .{326 return @intFromEnum(limit) - remaining;
320 .first = first,327}
321 .middle = middle,328
322 .last = r.buffer,329fn readVecInner(r: *Reader, middle: []const []u8, first: []u8, remaining: usize) Error!usize {
323 },330 var wrapper: Writer.VectorWrapper = .{
324 .writer = .{331 .it = .{
325 .buffer = if (first.len >= r.buffer.len) first else r.buffer,332 .first = first,
326 .vtable = Writer.VectorWrapper.vtable,333 .middle = middle,
327 },334 .last = r.buffer,
328 };335 },
329 var n = r.vtable.stream(r, &wrapper.writer, .limited(remaining)) catch |err| switch (err) {336 .writer = .{
330 error.WriteFailed => {337 .buffer = if (first.len >= r.buffer.len) first else r.buffer,
331 assert(!wrapper.used);338 .vtable = Writer.VectorWrapper.vtable,
332 if (wrapper.writer.buffer.ptr == first.ptr) {339 },
333 remaining -= wrapper.writer.end;340 };
334 } else {341 // If the limit may pass beyond user buffer into Reader buffer, use
335 assert(wrapper.writer.end <= r.buffer.len);342 // unlimited, allowing the Reader buffer to fill.
336 r.end = wrapper.writer.end;343 const limit: Limit = l: {
337 }344 var n: usize = first.len;
338 break;345 for (middle) |m| n += m.len;
339 },346 break :l if (remaining >= n) .unlimited else .limited(remaining);
340 else => |e| return e,347 };
341 };348 var n = r.vtable.stream(r, &wrapper.writer, limit) catch |err| switch (err) {
342 if (!wrapper.used) {349 error.WriteFailed => {
350 assert(!wrapper.used);
343 if (wrapper.writer.buffer.ptr == first.ptr) {351 if (wrapper.writer.buffer.ptr == first.ptr) {
344 remaining -= n;352 return remaining - wrapper.writer.end;
345 } else {353 } else {
346 assert(n <= r.buffer.len);354 assert(wrapper.writer.end <= r.buffer.len);
347 r.end = n;355 r.end = wrapper.writer.end;
356 return remaining;
348 }357 }
349 break;358 },
350 }359 else => |e| return e,
351 if (n < first.len) {360 };
352 remaining -= n;361 if (!wrapper.used) {
353 break;362 if (wrapper.writer.buffer.ptr == first.ptr) {
363 return remaining - n;
364 } else {
365 assert(n <= r.buffer.len);
366 r.end = n;
367 return remaining;
354 }368 }
355 remaining -= first.len;369 }
356 n -= first.len;370 if (n < first.len) return remaining - n;
357 for (middle) |mid| {371 var result = remaining - first.len;
358 if (n < mid.len) {372 n -= first.len;
359 remaining -= n;373 for (middle) |mid| {
360 break;374 if (n < mid.len) {
361 }375 return result - n;
362 remaining -= mid.len;
363 n -= mid.len;
364 }376 }
365 assert(n <= r.buffer.len);377 result -= mid.len;
366 r.end = n;378 n -= mid.len;
367 break;
368 }379 }
369 return @intFromEnum(limit) - remaining;380 assert(n <= r.buffer.len);
381 r.end = n;
382 return result;
370}383}
371384
372pub fn buffered(r: *Reader) []u8 {385pub fn buffered(r: *Reader) []u8 {
lib/std/Io/Writer.zig+40-4
...@@ -483,7 +483,7 @@ pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void {...@@ -483,7 +483,7 @@ pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void {
483483
484 // Deal with any left over splats484 // Deal with any left over splats
485 if (data.len != 0 and truncate < data[index].len * splat) {485 if (data.len != 0 and truncate < data[index].len * splat) {
486 std.debug.assert(index == data.len - 1);486 assert(index == data.len - 1);
487 var remaining_splat = splat;487 var remaining_splat = splat;
488 while (true) {488 while (true) {
489 remaining_splat -= truncate / data[index].len;489 remaining_splat -= truncate / data[index].len;
...@@ -840,11 +840,11 @@ pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.builtin.Endian...@@ -840,11 +840,11 @@ pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.builtin.Endian
840 .auto => @compileError("ill-defined memory layout"),840 .auto => @compileError("ill-defined memory layout"),
841 .@"extern" => {841 .@"extern" => {
842 if (native_endian == endian) {842 if (native_endian == endian) {
843 return w.writeStruct(value);843 return w.writeAll(@ptrCast((&value)[0..1]));
844 } else {844 } else {
845 var copy = value;845 var copy = value;
846 std.mem.byteSwapAllFields(@TypeOf(value), &copy);846 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
847 return w.writeStruct(copy);847 return w.writeAll(@ptrCast((&copy)[0..1]));
848 }848 }
849 },849 },
850 .@"packed" => {850 .@"packed" => {
...@@ -2475,6 +2475,18 @@ pub const Allocating = struct {...@@ -2475,6 +2475,18 @@ pub const Allocating = struct {
2475 return result;2475 return result;
2476 }2476 }
24772477
2478 pub fn ensureUnusedCapacity(a: *Allocating, additional_count: usize) Allocator.Error!void {
2479 var list = a.toArrayList();
2480 defer a.setArrayList(list);
2481 return list.ensureUnusedCapacity(a.allocator, additional_count);
2482 }
2483
2484 pub fn ensureTotalCapacity(a: *Allocating, new_capacity: usize) Allocator.Error!void {
2485 var list = a.toArrayList();
2486 defer a.setArrayList(list);
2487 return list.ensureTotalCapacity(a.allocator, new_capacity);
2488 }
2489
2478 pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {2490 pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {
2479 var list = a.toArrayList();2491 var list = a.toArrayList();
2480 defer a.setArrayList(list);2492 defer a.setArrayList(list);
...@@ -2594,8 +2606,32 @@ test "allocating sendFile" {...@@ -2594,8 +2606,32 @@ test "allocating sendFile" {
2594 var file_reader = file_writer.moveToReader();2606 var file_reader = file_writer.moveToReader();
2595 try file_reader.seekTo(0);2607 try file_reader.seekTo(0);
25962608
2597 var allocating: std.io.Writer.Allocating = .init(std.testing.allocator);2609 var allocating: std.io.Writer.Allocating = .init(testing.allocator);
2598 defer allocating.deinit();2610 defer allocating.deinit();
25992611
2600 _ = try file_reader.interface.streamRemaining(&allocating.writer);2612 _ = try file_reader.interface.streamRemaining(&allocating.writer);
2601}2613}
2614
2615test writeStruct {
2616 var buffer: [16]u8 = undefined;
2617 const S = extern struct { a: u64, b: u32, c: u32 };
2618 const s: S = .{ .a = 1, .b = 2, .c = 3 };
2619 {
2620 var w: Writer = .fixed(&buffer);
2621 try w.writeStruct(s, .little);
2622 try testing.expectEqualSlices(u8, &.{
2623 1, 0, 0, 0, 0, 0, 0, 0, //
2624 2, 0, 0, 0, //
2625 3, 0, 0, 0, //
2626 }, &buffer);
2627 }
2628 {
2629 var w: Writer = .fixed(&buffer);
2630 try w.writeStruct(s, .big);
2631 try testing.expectEqualSlices(u8, &.{
2632 0, 0, 0, 0, 0, 0, 0, 1, //
2633 0, 0, 0, 2, //
2634 0, 0, 0, 3, //
2635 }, &buffer);
2636 }
2637}
lib/std/zig.zig+28-38
...@@ -2,6 +2,12 @@...@@ -2,6 +2,12 @@
2//! source lives here. These APIs are provided as-is and have absolutely no API2//! source lives here. These APIs are provided as-is and have absolutely no API
3//! guarantees whatsoever.3//! guarantees whatsoever.
44
5const std = @import("std.zig");
6const tokenizer = @import("zig/tokenizer.zig");
7const assert = std.debug.assert;
8const Allocator = std.mem.Allocator;
9const Writer = std.Io.Writer;
10
5pub const ErrorBundle = @import("zig/ErrorBundle.zig");11pub const ErrorBundle = @import("zig/ErrorBundle.zig");
6pub const Server = @import("zig/Server.zig");12pub const Server = @import("zig/Server.zig");
7pub const Client = @import("zig/Client.zig");13pub const Client = @import("zig/Client.zig");
...@@ -355,11 +361,6 @@ pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![...@@ -355,11 +361,6 @@ pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![
355 return buffer.toOwnedSlice();361 return buffer.toOwnedSlice();
356}362}
357363
358const std = @import("std.zig");
359const tokenizer = @import("zig/tokenizer.zig");
360const assert = std.debug.assert;
361const Allocator = std.mem.Allocator;
362
363/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.364/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
364///365///
365/// See also `fmtIdFlags`.366/// See also `fmtIdFlags`.
...@@ -425,7 +426,7 @@ pub const FormatId = struct {...@@ -425,7 +426,7 @@ pub const FormatId = struct {
425 };426 };
426427
427 /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.428 /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
428 fn render(ctx: FormatId, writer: *std.io.Writer) std.io.Writer.Error!void {429 fn render(ctx: FormatId, writer: *Writer) Writer.Error!void {
429 const bytes = ctx.bytes;430 const bytes = ctx.bytes;
430 if (isValidId(bytes) and431 if (isValidId(bytes) and
431 (ctx.flags.allow_primitive or !std.zig.isPrimitive(bytes)) and432 (ctx.flags.allow_primitive or !std.zig.isPrimitive(bytes)) and
...@@ -463,7 +464,7 @@ test fmtChar {...@@ -463,7 +464,7 @@ test fmtChar {
463}464}
464465
465/// Print the string as escaped contents of a double quoted string.466/// Print the string as escaped contents of a double quoted string.
466pub fn stringEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!void {467pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
467 for (bytes) |byte| switch (byte) {468 for (bytes) |byte| switch (byte) {
468 '\n' => try w.writeAll("\\n"),469 '\n' => try w.writeAll("\\n"),
469 '\r' => try w.writeAll("\\r"),470 '\r' => try w.writeAll("\\r"),
...@@ -480,7 +481,7 @@ pub fn stringEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!vo...@@ -480,7 +481,7 @@ pub fn stringEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!vo
480}481}
481482
482/// Print the string as escaped contents of a single-quoted string.483/// Print the string as escaped contents of a single-quoted string.
483pub fn charEscape(bytes: []const u8, w: *std.io.Writer) std.io.Writer.Error!void {484pub fn charEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
484 for (bytes) |byte| switch (byte) {485 for (bytes) |byte| switch (byte) {
485 '\n' => try w.writeAll("\\n"),486 '\n' => try w.writeAll("\\n"),
486 '\r' => try w.writeAll("\\r"),487 '\r' => try w.writeAll("\\r"),
...@@ -529,20 +530,16 @@ test isUnderscore {...@@ -529,20 +530,16 @@ test isUnderscore {
529 try std.testing.expect(!isUnderscore("\\x5f"));530 try std.testing.expect(!isUnderscore("\\x5f"));
530}531}
531532
532pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?usize) ![:0]u8 {533pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader) ![:0]u8 {
533 const source_code = input.readToEndAllocOptions(534 var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty;
534 gpa,535 defer buffer.deinit(gpa);
535 max_src_size,536
536 size_hint,537 if (file_reader.getSize()) |size| {
537 .of(u8),538 const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong;
538 0,539 try buffer.ensureTotalCapacityPrecise(gpa, casted_size);
539 ) catch |err| switch (err) {540 } else |_| {}
540 error.ConnectionResetByPeer => unreachable,541
541 error.ConnectionTimedOut => unreachable,542 try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size));
542 error.NotOpenForReading => unreachable,
543 else => |e| return e,
544 };
545 errdefer gpa.free(source_code);
546543
547 // Detect unsupported file types with their Byte Order Mark544 // Detect unsupported file types with their Byte Order Mark
548 const unsupported_boms = [_][]const u8{545 const unsupported_boms = [_][]const u8{
...@@ -551,30 +548,23 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?...@@ -551,30 +548,23 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, input: std.fs.File, size_hint: ?
551 "\xfe\xff", // UTF-16 big endian548 "\xfe\xff", // UTF-16 big endian
552 };549 };
553 for (unsupported_boms) |bom| {550 for (unsupported_boms) |bom| {
554 if (std.mem.startsWith(u8, source_code, bom)) {551 if (std.mem.startsWith(u8, buffer.items, bom)) {
555 return error.UnsupportedEncoding;552 return error.UnsupportedEncoding;
556 }553 }
557 }554 }
558555
559 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8556 // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
560 if (std.mem.startsWith(u8, source_code, "\xff\xfe")) {557 if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) {
561 if (source_code.len % 2 != 0) return error.InvalidEncoding;558 if (buffer.items.len % 2 != 0) return error.InvalidEncoding;
562 // TODO: after wrangle-writer-buffering branch is merged,559 return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(buffer.items)) catch |err| switch (err) {
563 // avoid this unnecessary allocation
564 const aligned_copy = try gpa.alloc(u16, source_code.len / 2);
565 defer gpa.free(aligned_copy);
566 @memcpy(std.mem.sliceAsBytes(aligned_copy), source_code);
567 const source_code_utf8 = std.unicode.utf16LeToUtf8AllocZ(gpa, aligned_copy) catch |err| switch (err) {
568 error.DanglingSurrogateHalf => error.UnsupportedEncoding,560 error.DanglingSurrogateHalf => error.UnsupportedEncoding,
569 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,561 error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
570 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,562 error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
571 else => |e| return e,563 else => |e| return e,
572 };564 };
573 gpa.free(source_code);
574 return source_code_utf8;
575 }565 }
576566
577 return source_code;567 return buffer.toOwnedSliceSentinel(gpa, 0);
578}568}
579569
580pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {570pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
...@@ -621,7 +611,7 @@ pub fn parseTargetQueryOrReportFatalError(...@@ -621,7 +611,7 @@ pub fn parseTargetQueryOrReportFatalError(
621 var help_text = std.ArrayList(u8).init(allocator);611 var help_text = std.ArrayList(u8).init(allocator);
622 defer help_text.deinit();612 defer help_text.deinit();
623 for (diags.arch.?.allCpuModels()) |cpu| {613 for (diags.arch.?.allCpuModels()) |cpu| {
624 help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help;614 help_text.print(" {s}\n", .{cpu.name}) catch break :help;
625 }615 }
626 std.log.info("available CPUs for architecture '{s}':\n{s}", .{616 std.log.info("available CPUs for architecture '{s}':\n{s}", .{
627 @tagName(diags.arch.?), help_text.items,617 @tagName(diags.arch.?), help_text.items,
...@@ -634,7 +624,7 @@ pub fn parseTargetQueryOrReportFatalError(...@@ -634,7 +624,7 @@ pub fn parseTargetQueryOrReportFatalError(
634 var help_text = std.ArrayList(u8).init(allocator);624 var help_text = std.ArrayList(u8).init(allocator);
635 defer help_text.deinit();625 defer help_text.deinit();
636 for (diags.arch.?.allFeaturesList()) |feature| {626 for (diags.arch.?.allFeaturesList()) |feature| {
637 help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help;627 help_text.print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help;
638 }628 }
639 std.log.info("available CPU features for architecture '{s}':\n{s}", .{629 std.log.info("available CPU features for architecture '{s}':\n{s}", .{
640 @tagName(diags.arch.?), help_text.items,630 @tagName(diags.arch.?), help_text.items,
...@@ -647,7 +637,7 @@ pub fn parseTargetQueryOrReportFatalError(...@@ -647,7 +637,7 @@ pub fn parseTargetQueryOrReportFatalError(
647 var help_text = std.ArrayList(u8).init(allocator);637 var help_text = std.ArrayList(u8).init(allocator);
648 defer help_text.deinit();638 defer help_text.deinit();
649 inline for (@typeInfo(std.Target.ObjectFormat).@"enum".fields) |field| {639 inline for (@typeInfo(std.Target.ObjectFormat).@"enum".fields) |field| {
650 help_text.writer().print(" {s}\n", .{field.name}) catch break :help;640 help_text.print(" {s}\n", .{field.name}) catch break :help;
651 }641 }
652 std.log.info("available object formats:\n{s}", .{help_text.items});642 std.log.info("available object formats:\n{s}", .{help_text.items});
653 }643 }
...@@ -658,7 +648,7 @@ pub fn parseTargetQueryOrReportFatalError(...@@ -658,7 +648,7 @@ pub fn parseTargetQueryOrReportFatalError(
658 var help_text = std.ArrayList(u8).init(allocator);648 var help_text = std.ArrayList(u8).init(allocator);
659 defer help_text.deinit();649 defer help_text.deinit();
660 inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".fields) |field| {650 inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".fields) |field| {
661 help_text.writer().print(" {s}\n", .{field.name}) catch break :help;651 help_text.print(" {s}\n", .{field.name}) catch break :help;
662 }652 }
663 std.log.info("available architectures:\n{s} native\n", .{help_text.items});653 std.log.info("available architectures:\n{s} native\n", .{help_text.items});
664 }654 }
lib/std/zig/Ast.zig+89-97
...@@ -4,6 +4,16 @@...@@ -4,6 +4,16 @@
4//! For Zon syntax, the root node is at nodes[0] and contains lhs as the node4//! For Zon syntax, the root node is at nodes[0] and contains lhs as the node
5//! index of the main expression.5//! index of the main expression.
66
7const std = @import("../std.zig");
8const assert = std.debug.assert;
9const testing = std.testing;
10const mem = std.mem;
11const Token = std.zig.Token;
12const Ast = @This();
13const Allocator = std.mem.Allocator;
14const Parse = @import("Parse.zig");
15const Writer = std.Io.Writer;
16
7/// Reference to externally-owned data.17/// Reference to externally-owned data.
8source: [:0]const u8,18source: [:0]const u8,
919
...@@ -128,12 +138,6 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void {...@@ -128,12 +138,6 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void {
128 tree.* = undefined;138 tree.* = undefined;
129}139}
130140
131pub const RenderError = error{
132 /// Ran out of memory allocating call stack frames to complete rendering, or
133 /// ran out of memory allocating space in the output buffer.
134 OutOfMemory,
135};
136
137pub const Mode = enum { zig, zon };141pub const Mode = enum { zig, zon };
138142
139/// Result should be freed with tree.deinit() when there are143/// Result should be freed with tree.deinit() when there are
...@@ -199,27 +203,25 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A...@@ -199,27 +203,25 @@ pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!A
199203
200/// `gpa` is used for allocating the resulting formatted source code.204/// `gpa` is used for allocating the resulting formatted source code.
201/// Caller owns the returned slice of bytes, allocated with `gpa`.205/// Caller owns the returned slice of bytes, allocated with `gpa`.
202pub fn render(tree: Ast, gpa: Allocator) RenderError![]u8 {206pub fn renderAlloc(tree: Ast, gpa: Allocator) error{OutOfMemory}![]u8 {
203 var buffer = std.ArrayList(u8).init(gpa);207 var aw: std.io.Writer.Allocating = .init(gpa);
204 defer buffer.deinit();208 defer aw.deinit();
205209 render(tree, gpa, &aw.writer, .{}) catch |err| switch (err) {
206 try tree.renderToArrayList(&buffer, .{});210 error.WriteFailed, error.OutOfMemory => return error.OutOfMemory,
207 return buffer.toOwnedSlice();211 };
212 return aw.toOwnedSlice();
208}213}
209214
210pub const Fixups = private_render.Fixups;215pub const Render = @import("Ast/Render.zig");
211216
212pub fn renderToArrayList(tree: Ast, buffer: *std.ArrayList(u8), fixups: Fixups) RenderError!void {217pub fn render(tree: Ast, gpa: Allocator, w: *Writer, fixups: Render.Fixups) Render.Error!void {
213 return @import("./render.zig").renderTree(buffer, tree, fixups);218 return Render.renderTree(gpa, w, tree, fixups);
214}219}
215220
216/// Returns an extra offset for column and byte offset of errors that221/// Returns an extra offset for column and byte offset of errors that
217/// should point after the token in the error message.222/// should point after the token in the error message.
218pub fn errorOffset(tree: Ast, parse_error: Error) u32 {223pub fn errorOffset(tree: Ast, parse_error: Error) u32 {
219 return if (parse_error.token_is_prev)224 return if (parse_error.token_is_prev) @intCast(tree.tokenSlice(parse_error.token).len) else 0;
220 @as(u32, @intCast(tree.tokenSlice(parse_error.token).len))
221 else
222 0;
223}225}
224226
225pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location {227pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location {
...@@ -318,254 +320,254 @@ pub fn rootDecls(tree: Ast) []const Node.Index {...@@ -318,254 +320,254 @@ pub fn rootDecls(tree: Ast) []const Node.Index {
318 }320 }
319}321}
320322
321pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {323pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void {
322 switch (parse_error.tag) {324 switch (parse_error.tag) {
323 .asterisk_after_ptr_deref => {325 .asterisk_after_ptr_deref => {
324 // Note that the token will point at the `.*` but ideally the source326 // Note that the token will point at the `.*` but ideally the source
325 // location would point to the `*` after the `.*`.327 // location would point to the `*` after the `.*`.
326 return stream.writeAll("'.*' cannot be followed by '*'; are you missing a space?");328 return w.writeAll("'.*' cannot be followed by '*'; are you missing a space?");
327 },329 },
328 .chained_comparison_operators => {330 .chained_comparison_operators => {
329 return stream.writeAll("comparison operators cannot be chained");331 return w.writeAll("comparison operators cannot be chained");
330 },332 },
331 .decl_between_fields => {333 .decl_between_fields => {
332 return stream.writeAll("declarations are not allowed between container fields");334 return w.writeAll("declarations are not allowed between container fields");
333 },335 },
334 .expected_block => {336 .expected_block => {
335 return stream.print("expected block, found '{s}'", .{337 return w.print("expected block, found '{s}'", .{
336 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),338 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
337 });339 });
338 },340 },
339 .expected_block_or_assignment => {341 .expected_block_or_assignment => {
340 return stream.print("expected block or assignment, found '{s}'", .{342 return w.print("expected block or assignment, found '{s}'", .{
341 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),343 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
342 });344 });
343 },345 },
344 .expected_block_or_expr => {346 .expected_block_or_expr => {
345 return stream.print("expected block or expression, found '{s}'", .{347 return w.print("expected block or expression, found '{s}'", .{
346 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),348 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
347 });349 });
348 },350 },
349 .expected_block_or_field => {351 .expected_block_or_field => {
350 return stream.print("expected block or field, found '{s}'", .{352 return w.print("expected block or field, found '{s}'", .{
351 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),353 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
352 });354 });
353 },355 },
354 .expected_container_members => {356 .expected_container_members => {
355 return stream.print("expected test, comptime, var decl, or container field, found '{s}'", .{357 return w.print("expected test, comptime, var decl, or container field, found '{s}'", .{
356 tree.tokenTag(parse_error.token).symbol(),358 tree.tokenTag(parse_error.token).symbol(),
357 });359 });
358 },360 },
359 .expected_expr => {361 .expected_expr => {
360 return stream.print("expected expression, found '{s}'", .{362 return w.print("expected expression, found '{s}'", .{
361 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),363 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
362 });364 });
363 },365 },
364 .expected_expr_or_assignment => {366 .expected_expr_or_assignment => {
365 return stream.print("expected expression or assignment, found '{s}'", .{367 return w.print("expected expression or assignment, found '{s}'", .{
366 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),368 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
367 });369 });
368 },370 },
369 .expected_expr_or_var_decl => {371 .expected_expr_or_var_decl => {
370 return stream.print("expected expression or var decl, found '{s}'", .{372 return w.print("expected expression or var decl, found '{s}'", .{
371 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),373 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
372 });374 });
373 },375 },
374 .expected_fn => {376 .expected_fn => {
375 return stream.print("expected function, found '{s}'", .{377 return w.print("expected function, found '{s}'", .{
376 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),378 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
377 });379 });
378 },380 },
379 .expected_inlinable => {381 .expected_inlinable => {
380 return stream.print("expected 'while' or 'for', found '{s}'", .{382 return w.print("expected 'while' or 'for', found '{s}'", .{
381 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),383 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
382 });384 });
383 },385 },
384 .expected_labelable => {386 .expected_labelable => {
385 return stream.print("expected 'while', 'for', 'inline', or '{{', found '{s}'", .{387 return w.print("expected 'while', 'for', 'inline', or '{{', found '{s}'", .{
386 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),388 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
387 });389 });
388 },390 },
389 .expected_param_list => {391 .expected_param_list => {
390 return stream.print("expected parameter list, found '{s}'", .{392 return w.print("expected parameter list, found '{s}'", .{
391 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),393 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
392 });394 });
393 },395 },
394 .expected_prefix_expr => {396 .expected_prefix_expr => {
395 return stream.print("expected prefix expression, found '{s}'", .{397 return w.print("expected prefix expression, found '{s}'", .{
396 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),398 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
397 });399 });
398 },400 },
399 .expected_primary_type_expr => {401 .expected_primary_type_expr => {
400 return stream.print("expected primary type expression, found '{s}'", .{402 return w.print("expected primary type expression, found '{s}'", .{
401 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),403 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
402 });404 });
403 },405 },
404 .expected_pub_item => {406 .expected_pub_item => {
405 return stream.writeAll("expected function or variable declaration after pub");407 return w.writeAll("expected function or variable declaration after pub");
406 },408 },
407 .expected_return_type => {409 .expected_return_type => {
408 return stream.print("expected return type expression, found '{s}'", .{410 return w.print("expected return type expression, found '{s}'", .{
409 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),411 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
410 });412 });
411 },413 },
412 .expected_semi_or_else => {414 .expected_semi_or_else => {
413 return stream.writeAll("expected ';' or 'else' after statement");415 return w.writeAll("expected ';' or 'else' after statement");
414 },416 },
415 .expected_semi_or_lbrace => {417 .expected_semi_or_lbrace => {
416 return stream.writeAll("expected ';' or block after function prototype");418 return w.writeAll("expected ';' or block after function prototype");
417 },419 },
418 .expected_statement => {420 .expected_statement => {
419 return stream.print("expected statement, found '{s}'", .{421 return w.print("expected statement, found '{s}'", .{
420 tree.tokenTag(parse_error.token).symbol(),422 tree.tokenTag(parse_error.token).symbol(),
421 });423 });
422 },424 },
423 .expected_suffix_op => {425 .expected_suffix_op => {
424 return stream.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{426 return w.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{
425 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),427 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
426 });428 });
427 },429 },
428 .expected_type_expr => {430 .expected_type_expr => {
429 return stream.print("expected type expression, found '{s}'", .{431 return w.print("expected type expression, found '{s}'", .{
430 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),432 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
431 });433 });
432 },434 },
433 .expected_var_decl => {435 .expected_var_decl => {
434 return stream.print("expected variable declaration, found '{s}'", .{436 return w.print("expected variable declaration, found '{s}'", .{
435 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),437 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
436 });438 });
437 },439 },
438 .expected_var_decl_or_fn => {440 .expected_var_decl_or_fn => {
439 return stream.print("expected variable declaration or function, found '{s}'", .{441 return w.print("expected variable declaration or function, found '{s}'", .{
440 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),442 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
441 });443 });
442 },444 },
443 .expected_loop_payload => {445 .expected_loop_payload => {
444 return stream.print("expected loop payload, found '{s}'", .{446 return w.print("expected loop payload, found '{s}'", .{
445 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),447 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
446 });448 });
447 },449 },
448 .expected_container => {450 .expected_container => {
449 return stream.print("expected a struct, enum or union, found '{s}'", .{451 return w.print("expected a struct, enum or union, found '{s}'", .{
450 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),452 tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
451 });453 });
452 },454 },
453 .extern_fn_body => {455 .extern_fn_body => {
454 return stream.writeAll("extern functions have no body");456 return w.writeAll("extern functions have no body");
455 },457 },
456 .extra_addrspace_qualifier => {458 .extra_addrspace_qualifier => {
457 return stream.writeAll("extra addrspace qualifier");459 return w.writeAll("extra addrspace qualifier");
458 },460 },
459 .extra_align_qualifier => {461 .extra_align_qualifier => {
460 return stream.writeAll("extra align qualifier");462 return w.writeAll("extra align qualifier");
461 },463 },
462 .extra_allowzero_qualifier => {464 .extra_allowzero_qualifier => {
463 return stream.writeAll("extra allowzero qualifier");465 return w.writeAll("extra allowzero qualifier");
464 },466 },
465 .extra_const_qualifier => {467 .extra_const_qualifier => {
466 return stream.writeAll("extra const qualifier");468 return w.writeAll("extra const qualifier");
467 },469 },
468 .extra_volatile_qualifier => {470 .extra_volatile_qualifier => {
469 return stream.writeAll("extra volatile qualifier");471 return w.writeAll("extra volatile qualifier");
470 },472 },
471 .ptr_mod_on_array_child_type => {473 .ptr_mod_on_array_child_type => {
472 return stream.print("pointer modifier '{s}' not allowed on array child type", .{474 return w.print("pointer modifier '{s}' not allowed on array child type", .{
473 tree.tokenTag(parse_error.token).symbol(),475 tree.tokenTag(parse_error.token).symbol(),
474 });476 });
475 },477 },
476 .invalid_bit_range => {478 .invalid_bit_range => {
477 return stream.writeAll("bit range not allowed on slices and arrays");479 return w.writeAll("bit range not allowed on slices and arrays");
478 },480 },
479 .same_line_doc_comment => {481 .same_line_doc_comment => {
480 return stream.writeAll("same line documentation comment");482 return w.writeAll("same line documentation comment");
481 },483 },
482 .unattached_doc_comment => {484 .unattached_doc_comment => {
483 return stream.writeAll("unattached documentation comment");485 return w.writeAll("unattached documentation comment");
484 },486 },
485 .test_doc_comment => {487 .test_doc_comment => {
486 return stream.writeAll("documentation comments cannot be attached to tests");488 return w.writeAll("documentation comments cannot be attached to tests");
487 },489 },
488 .comptime_doc_comment => {490 .comptime_doc_comment => {
489 return stream.writeAll("documentation comments cannot be attached to comptime blocks");491 return w.writeAll("documentation comments cannot be attached to comptime blocks");
490 },492 },
491 .varargs_nonfinal => {493 .varargs_nonfinal => {
492 return stream.writeAll("function prototype has parameter after varargs");494 return w.writeAll("function prototype has parameter after varargs");
493 },495 },
494 .expected_continue_expr => {496 .expected_continue_expr => {
495 return stream.writeAll("expected ':' before while continue expression");497 return w.writeAll("expected ':' before while continue expression");
496 },498 },
497499
498 .expected_semi_after_decl => {500 .expected_semi_after_decl => {
499 return stream.writeAll("expected ';' after declaration");501 return w.writeAll("expected ';' after declaration");
500 },502 },
501 .expected_semi_after_stmt => {503 .expected_semi_after_stmt => {
502 return stream.writeAll("expected ';' after statement");504 return w.writeAll("expected ';' after statement");
503 },505 },
504 .expected_comma_after_field => {506 .expected_comma_after_field => {
505 return stream.writeAll("expected ',' after field");507 return w.writeAll("expected ',' after field");
506 },508 },
507 .expected_comma_after_arg => {509 .expected_comma_after_arg => {
508 return stream.writeAll("expected ',' after argument");510 return w.writeAll("expected ',' after argument");
509 },511 },
510 .expected_comma_after_param => {512 .expected_comma_after_param => {
511 return stream.writeAll("expected ',' after parameter");513 return w.writeAll("expected ',' after parameter");
512 },514 },
513 .expected_comma_after_initializer => {515 .expected_comma_after_initializer => {
514 return stream.writeAll("expected ',' after initializer");516 return w.writeAll("expected ',' after initializer");
515 },517 },
516 .expected_comma_after_switch_prong => {518 .expected_comma_after_switch_prong => {
517 return stream.writeAll("expected ',' after switch prong");519 return w.writeAll("expected ',' after switch prong");
518 },520 },
519 .expected_comma_after_for_operand => {521 .expected_comma_after_for_operand => {
520 return stream.writeAll("expected ',' after for operand");522 return w.writeAll("expected ',' after for operand");
521 },523 },
522 .expected_comma_after_capture => {524 .expected_comma_after_capture => {
523 return stream.writeAll("expected ',' after for capture");525 return w.writeAll("expected ',' after for capture");
524 },526 },
525 .expected_initializer => {527 .expected_initializer => {
526 return stream.writeAll("expected field initializer");528 return w.writeAll("expected field initializer");
527 },529 },
528 .mismatched_binary_op_whitespace => {530 .mismatched_binary_op_whitespace => {
529 return stream.print("binary operator '{s}' has whitespace on one side, but not the other", .{tree.tokenTag(parse_error.token).lexeme().?});531 return w.print("binary operator '{s}' has whitespace on one side, but not the other", .{tree.tokenTag(parse_error.token).lexeme().?});
530 },532 },
531 .invalid_ampersand_ampersand => {533 .invalid_ampersand_ampersand => {
532 return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");534 return w.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");
533 },535 },
534 .c_style_container => {536 .c_style_container => {
535 return stream.print("'{s} {s}' is invalid", .{537 return w.print("'{s} {s}' is invalid", .{
536 parse_error.extra.expected_tag.symbol(), tree.tokenSlice(parse_error.token),538 parse_error.extra.expected_tag.symbol(), tree.tokenSlice(parse_error.token),
537 });539 });
538 },540 },
539 .zig_style_container => {541 .zig_style_container => {
540 return stream.print("to declare a container do 'const {s} = {s}'", .{542 return w.print("to declare a container do 'const {s} = {s}'", .{
541 tree.tokenSlice(parse_error.token), parse_error.extra.expected_tag.symbol(),543 tree.tokenSlice(parse_error.token), parse_error.extra.expected_tag.symbol(),
542 });544 });
543 },545 },
544 .previous_field => {546 .previous_field => {
545 return stream.writeAll("field before declarations here");547 return w.writeAll("field before declarations here");
546 },548 },
547 .next_field => {549 .next_field => {
548 return stream.writeAll("field after declarations here");550 return w.writeAll("field after declarations here");
549 },551 },
550 .expected_var_const => {552 .expected_var_const => {
551 return stream.writeAll("expected 'var' or 'const' before variable declaration");553 return w.writeAll("expected 'var' or 'const' before variable declaration");
552 },554 },
553 .wrong_equal_var_decl => {555 .wrong_equal_var_decl => {
554 return stream.writeAll("variable initialized with '==' instead of '='");556 return w.writeAll("variable initialized with '==' instead of '='");
555 },557 },
556 .var_const_decl => {558 .var_const_decl => {
557 return stream.writeAll("use 'var' or 'const' to declare variable");559 return w.writeAll("use 'var' or 'const' to declare variable");
558 },560 },
559 .extra_for_capture => {561 .extra_for_capture => {
560 return stream.writeAll("extra capture in for loop");562 return w.writeAll("extra capture in for loop");
561 },563 },
562 .for_input_not_captured => {564 .for_input_not_captured => {
563 return stream.writeAll("for input is not captured");565 return w.writeAll("for input is not captured");
564 },566 },
565567
566 .invalid_byte => {568 .invalid_byte => {
567 const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..];569 const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..];
568 return stream.print("{s} contains invalid byte: '{f}'", .{570 return w.print("{s} contains invalid byte: '{f}'", .{
569 switch (tok_slice[0]) {571 switch (tok_slice[0]) {
570 '\'' => "character literal",572 '\'' => "character literal",
571 '"', '\\' => "string literal",573 '"', '\\' => "string literal",
...@@ -580,10 +582,10 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {...@@ -580,10 +582,10 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
580 const found_tag = tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev));582 const found_tag = tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev));
581 const expected_symbol = parse_error.extra.expected_tag.symbol();583 const expected_symbol = parse_error.extra.expected_tag.symbol();
582 switch (found_tag) {584 switch (found_tag) {
583 .invalid => return stream.print("expected '{s}', found invalid bytes", .{585 .invalid => return w.print("expected '{s}', found invalid bytes", .{
584 expected_symbol,586 expected_symbol,
585 }),587 }),
586 else => return stream.print("expected '{s}', found '{s}'", .{588 else => return w.print("expected '{s}', found '{s}'", .{
587 expected_symbol, found_tag.symbol(),589 expected_symbol, found_tag.symbol(),
588 }),590 }),
589 }591 }
...@@ -4136,17 +4138,7 @@ pub fn tokensToSpan(tree: *const Ast, start: Ast.TokenIndex, end: Ast.TokenIndex...@@ -4136,17 +4138,7 @@ pub fn tokensToSpan(tree: *const Ast, start: Ast.TokenIndex, end: Ast.TokenIndex
4136 return Span{ .start = start_off, .end = end_off, .main = tree.tokenStart(main) };4138 return Span{ .start = start_off, .end = end_off, .main = tree.tokenStart(main) };
4137}4139}
41384140
4139const std = @import("../std.zig");
4140const assert = std.debug.assert;
4141const testing = std.testing;
4142const mem = std.mem;
4143const Token = std.zig.Token;
4144const Ast = @This();
4145const Allocator = std.mem.Allocator;
4146const Parse = @import("Parse.zig");
4147const private_render = @import("./render.zig");
4148
4149test {4141test {
4150 _ = Parse;4142 _ = Parse;
4151 _ = private_render;4143 _ = Render;
4152}4144}
lib/std/zig/Ast/Render.zig created+3645
...@@ -0,0 +1,3645 @@
1const std = @import("../../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const meta = std.meta;
6const Ast = std.zig.Ast;
7const Token = std.zig.Token;
8const primitives = std.zig.primitives;
9const Writer = std.io.Writer;
10
11const Render = @This();
12
13gpa: Allocator,
14ais: *AutoIndentingStream,
15tree: Ast,
16fixups: Fixups,
17
18const indent_delta = 4;
19const asm_indent_delta = 2;
20
21pub const Error = error{
22 /// Ran out of memory allocating call stack frames to complete rendering.
23 OutOfMemory,
24 /// Transitive failure from
25 WriteFailed,
26};
27
28pub const Fixups = struct {
29 /// The key is the mut token (`var`/`const`) of the variable declaration
30 /// that should have a `_ = foo;` inserted afterwards.
31 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .empty,
32 /// The functions in this unordered set of AST fn decl nodes will render
33 /// with a function body of `@trap()` instead, with all parameters
34 /// discarded.
35 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
36 /// These global declarations will be omitted.
37 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
38 /// These expressions will be replaced with the string value.
39 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
40 /// The string value will be inserted directly after the node.
41 append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
42 /// These nodes will be replaced with a different node.
43 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
44 /// Change all identifier names matching the key to be value instead.
45 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .empty,
46
47 /// All `@import` builtin calls which refer to a file path will be prefixed
48 /// with this path.
49 rebase_imported_paths: ?[]const u8 = null,
50
51 pub fn count(f: Fixups) usize {
52 return f.unused_var_decls.count() +
53 f.gut_functions.count() +
54 f.omit_nodes.count() +
55 f.replace_nodes_with_string.count() +
56 f.append_string_after_node.count() +
57 f.replace_nodes_with_node.count() +
58 f.rename_identifiers.count() +
59 @intFromBool(f.rebase_imported_paths != null);
60 }
61
62 pub fn clearRetainingCapacity(f: *Fixups) void {
63 f.unused_var_decls.clearRetainingCapacity();
64 f.gut_functions.clearRetainingCapacity();
65 f.omit_nodes.clearRetainingCapacity();
66 f.replace_nodes_with_string.clearRetainingCapacity();
67 f.append_string_after_node.clearRetainingCapacity();
68 f.replace_nodes_with_node.clearRetainingCapacity();
69 f.rename_identifiers.clearRetainingCapacity();
70
71 f.rebase_imported_paths = null;
72 }
73
74 pub fn deinit(f: *Fixups, gpa: Allocator) void {
75 f.unused_var_decls.deinit(gpa);
76 f.gut_functions.deinit(gpa);
77 f.omit_nodes.deinit(gpa);
78 f.replace_nodes_with_string.deinit(gpa);
79 f.append_string_after_node.deinit(gpa);
80 f.replace_nodes_with_node.deinit(gpa);
81 f.rename_identifiers.deinit(gpa);
82 f.* = undefined;
83 }
84};
85
86pub fn renderTree(gpa: Allocator, w: *Writer, tree: Ast, fixups: Fixups) Error!void {
87 assert(tree.errors.len == 0); // Cannot render an invalid tree.
88 var auto_indenting_stream: AutoIndentingStream = .init(gpa, w, indent_delta);
89 defer auto_indenting_stream.deinit();
90 var r: Render = .{
91 .gpa = gpa,
92 .ais = &auto_indenting_stream,
93 .tree = tree,
94 .fixups = fixups,
95 };
96
97 // Render all the line comments at the beginning of the file.
98 const comment_end_loc = tree.tokenStart(0);
99 _ = try renderComments(&r, 0, comment_end_loc);
100
101 if (tree.tokenTag(0) == .container_doc_comment) {
102 try renderContainerDocComments(&r, 0);
103 }
104
105 switch (tree.mode) {
106 .zig => try renderMembers(&r, tree.rootDecls()),
107 .zon => {
108 try renderExpression(
109 &r,
110 tree.rootDecls()[0],
111 .newline,
112 );
113 },
114 }
115
116 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
117 try writeFixingWhitespace(auto_indenting_stream.underlying_writer, tree.source[disabled_offset..]);
118 }
119}
120
121/// Render all members in the given slice, keeping empty lines where appropriate
122fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
123 const tree = r.tree;
124 if (members.len == 0) return;
125 const container: Container = for (members) |member| {
126 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
127 } else .tuple;
128 try renderMember(r, container, members[0], .newline);
129 for (members[1..]) |member| {
130 try renderExtraNewline(r, member);
131 try renderMember(r, container, member, .newline);
132 }
133}
134
135const Container = enum {
136 @"enum",
137 tuple,
138 other,
139};
140
141fn renderMember(
142 r: *Render,
143 container: Container,
144 decl: Ast.Node.Index,
145 space: Space,
146) Error!void {
147 const tree = r.tree;
148 const ais = r.ais;
149 if (r.fixups.omit_nodes.contains(decl)) return;
150 try renderDocComments(r, tree.firstToken(decl));
151 switch (tree.nodeTag(decl)) {
152 .fn_decl => {
153 // Some examples:
154 // pub extern "foo" fn ...
155 // export fn ...
156 const fn_proto, const body_node = tree.nodeData(decl).node_and_node;
157 const fn_token = tree.nodeMainToken(fn_proto);
158 // Go back to the first token we should render here.
159 var i = fn_token;
160 while (i > 0) {
161 i -= 1;
162 switch (tree.tokenTag(i)) {
163 .keyword_extern,
164 .keyword_export,
165 .keyword_pub,
166 .string_literal,
167 .keyword_inline,
168 .keyword_noinline,
169 => continue,
170
171 else => {
172 i += 1;
173 break;
174 },
175 }
176 }
177
178 while (i < fn_token) : (i += 1) {
179 try renderToken(r, i, .space);
180 }
181 switch (tree.nodeTag(fn_proto)) {
182 .fn_proto_one, .fn_proto => {
183 var buf: [1]Ast.Node.Index = undefined;
184 const opt_callconv_expr = if (tree.nodeTag(fn_proto) == .fn_proto_one)
185 tree.fnProtoOne(&buf, fn_proto).ast.callconv_expr
186 else
187 tree.fnProto(fn_proto).ast.callconv_expr;
188
189 // Keep in sync with logic in `renderFnProto`. Search this file for the marker PROMOTE_CALLCONV_INLINE
190 if (opt_callconv_expr.unwrap()) |callconv_expr| {
191 if (tree.nodeTag(callconv_expr) == .enum_literal) {
192 if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) {
193 try ais.underlying_writer.writeAll("inline ");
194 }
195 }
196 }
197 },
198 .fn_proto_simple, .fn_proto_multi => {},
199 else => unreachable,
200 }
201 try renderExpression(r, fn_proto, .space);
202 if (r.fixups.gut_functions.contains(decl)) {
203 try ais.pushIndent(.normal);
204 const lbrace = tree.nodeMainToken(body_node);
205 try renderToken(r, lbrace, .newline);
206 try discardAllParams(r, fn_proto);
207 try ais.writeAll("@trap();");
208 ais.popIndent();
209 try ais.insertNewline();
210 try renderToken(r, tree.lastToken(body_node), space); // rbrace
211 } else if (r.fixups.unused_var_decls.count() != 0) {
212 try ais.pushIndent(.normal);
213 const lbrace = tree.nodeMainToken(body_node);
214 try renderToken(r, lbrace, .newline);
215
216 var fn_proto_buf: [1]Ast.Node.Index = undefined;
217 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
218 var it = full_fn_proto.iterate(&tree);
219 while (it.next()) |param| {
220 const name_ident = param.name_token.?;
221 assert(tree.tokenTag(name_ident) == .identifier);
222 if (r.fixups.unused_var_decls.contains(name_ident)) {
223 try ais.writeAll("_ = ");
224 try ais.writeAll(tokenSliceForRender(r.tree, name_ident));
225 try ais.writeAll(";\n");
226 }
227 }
228 var statements_buf: [2]Ast.Node.Index = undefined;
229 const statements = tree.blockStatements(&statements_buf, body_node).?;
230 return finishRenderBlock(r, body_node, statements, space);
231 } else {
232 return renderExpression(r, body_node, space);
233 }
234 },
235 .fn_proto_simple,
236 .fn_proto_multi,
237 .fn_proto_one,
238 .fn_proto,
239 => {
240 // Extern function prototypes are parsed as these tags.
241 // Go back to the first token we should render here.
242 const fn_token = tree.nodeMainToken(decl);
243 var i = fn_token;
244 while (i > 0) {
245 i -= 1;
246 switch (tree.tokenTag(i)) {
247 .keyword_extern,
248 .keyword_export,
249 .keyword_pub,
250 .string_literal,
251 .keyword_inline,
252 .keyword_noinline,
253 => continue,
254
255 else => {
256 i += 1;
257 break;
258 },
259 }
260 }
261 while (i < fn_token) : (i += 1) {
262 try renderToken(r, i, .space);
263 }
264 try renderExpression(r, decl, .none);
265 return renderToken(r, tree.lastToken(decl) + 1, space); // semicolon
266 },
267
268 .global_var_decl,
269 .local_var_decl,
270 .simple_var_decl,
271 .aligned_var_decl,
272 => {
273 try ais.pushSpace(.semicolon);
274 try renderVarDecl(r, tree.fullVarDecl(decl).?, false, .semicolon);
275 ais.popSpace();
276 },
277
278 .test_decl => {
279 const test_token = tree.nodeMainToken(decl);
280 const opt_name_token, const block_node = tree.nodeData(decl).opt_token_and_node;
281 try renderToken(r, test_token, .space);
282 if (opt_name_token.unwrap()) |name_token| {
283 switch (tree.tokenTag(name_token)) {
284 .string_literal => try renderToken(r, name_token, .space),
285 .identifier => try renderIdentifier(r, name_token, .space, .preserve_when_shadowing),
286 else => unreachable,
287 }
288 }
289 try renderExpression(r, block_node, space);
290 },
291
292 .container_field_init,
293 .container_field_align,
294 .container_field,
295 => return renderContainerField(r, container, tree.fullContainerField(decl).?, space),
296
297 .@"comptime" => return renderExpression(r, decl, space),
298
299 .root => unreachable,
300 else => unreachable,
301 }
302}
303
304/// Render all expressions in the slice, keeping empty lines where appropriate
305fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
306 if (expressions.len == 0) return;
307 try renderExpression(r, expressions[0], space);
308 for (expressions[1..]) |expression| {
309 try renderExtraNewline(r, expression);
310 try renderExpression(r, expression, space);
311 }
312}
313
314fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
315 const tree = r.tree;
316 const ais = r.ais;
317 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
318 try ais.writeAll(replacement);
319 try renderOnlySpace(r, space);
320 return;
321 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
322 return renderExpression(r, replacement, space);
323 }
324 switch (tree.nodeTag(node)) {
325 .identifier => {
326 const token_index = tree.nodeMainToken(node);
327 return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
328 },
329
330 .number_literal,
331 .char_literal,
332 .unreachable_literal,
333 .anyframe_literal,
334 .string_literal,
335 => return renderToken(r, tree.nodeMainToken(node), space),
336
337 .multiline_string_literal => {
338 try ais.maybeInsertNewline();
339
340 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
341 for (first_tok..last_tok + 1) |i| {
342 try renderToken(r, @intCast(i), .newline);
343 }
344
345 const next_token = last_tok + 1;
346 const next_token_tag = tree.tokenTag(next_token);
347
348 // dedent the next thing that comes after a multiline string literal
349 if (!ais.indentStackEmpty() and
350 next_token_tag != .colon and
351 ((next_token_tag != .semicolon and next_token_tag != .comma) or
352 ais.lastSpaceModeIndent() < ais.currentIndent()))
353 {
354 ais.popIndent();
355 try ais.pushIndent(.normal);
356 }
357
358 switch (space) {
359 .none, .space, .newline, .skip => {},
360 .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
361 .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
362 .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space),
363 }
364 },
365
366 .error_value => {
367 const main_token = tree.nodeMainToken(node);
368 try renderToken(r, main_token, .none);
369 try renderToken(r, main_token + 1, .none);
370 return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
371 },
372
373 .block_two,
374 .block_two_semicolon,
375 .block,
376 .block_semicolon,
377 => {
378 var buf: [2]Ast.Node.Index = undefined;
379 const statements = tree.blockStatements(&buf, node).?;
380 return renderBlock(r, node, statements, space);
381 },
382
383 .@"errdefer" => {
384 const defer_token = tree.nodeMainToken(node);
385 const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
386
387 try renderToken(r, defer_token, .space);
388 if (maybe_payload_token.unwrap()) |payload_token| {
389 try renderToken(r, payload_token - 1, .none); // |
390 try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
391 try renderToken(r, payload_token + 1, .space); // |
392 }
393 return renderExpression(r, expr, space);
394 },
395
396 .@"defer",
397 .@"comptime",
398 .@"nosuspend",
399 .@"suspend",
400 => {
401 const main_token = tree.nodeMainToken(node);
402 const item = tree.nodeData(node).node;
403 try renderToken(r, main_token, .space);
404 return renderExpression(r, item, space);
405 },
406
407 .@"catch" => {
408 const main_token = tree.nodeMainToken(node);
409 const lhs, const rhs = tree.nodeData(node).node_and_node;
410 const fallback_first = tree.firstToken(rhs);
411
412 const same_line = tree.tokensOnSameLine(main_token, fallback_first);
413 const after_op_space = if (same_line) Space.space else Space.newline;
414
415 try renderExpression(r, lhs, .space); // target
416
417 try ais.pushIndent(.normal);
418 if (tree.tokenTag(fallback_first - 1) == .pipe) {
419 try renderToken(r, main_token, .space); // catch keyword
420 try renderToken(r, main_token + 1, .none); // pipe
421 try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
422 try renderToken(r, main_token + 3, after_op_space); // pipe
423 } else {
424 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
425 try renderToken(r, main_token, after_op_space); // catch keyword
426 }
427 try renderExpression(r, rhs, space); // fallback
428 ais.popIndent();
429 },
430
431 .field_access => {
432 const lhs, const name_token = tree.nodeData(node).node_and_token;
433 const dot_token = name_token - 1;
434
435 try ais.pushIndent(.field_access);
436 try renderExpression(r, lhs, .none);
437
438 // Allow a line break between the lhs and the dot if the lhs and rhs
439 // are on different lines.
440 const lhs_last_token = tree.lastToken(lhs);
441 const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
442 if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline();
443
444 try renderToken(r, dot_token, .none);
445
446 try renderIdentifier(r, name_token, space, .eagerly_unquote); // field
447 ais.popIndent();
448 },
449
450 .error_union,
451 .switch_range,
452 => {
453 const lhs, const rhs = tree.nodeData(node).node_and_node;
454 try renderExpression(r, lhs, .none);
455 try renderToken(r, tree.nodeMainToken(node), .none);
456 return renderExpression(r, rhs, space);
457 },
458 .for_range => {
459 const start, const opt_end = tree.nodeData(node).node_and_opt_node;
460 try renderExpression(r, start, .none);
461 if (opt_end.unwrap()) |end| {
462 try renderToken(r, tree.nodeMainToken(node), .none);
463 return renderExpression(r, end, space);
464 } else {
465 return renderToken(r, tree.nodeMainToken(node), space);
466 }
467 },
468
469 .assign,
470 .assign_bit_and,
471 .assign_bit_or,
472 .assign_shl,
473 .assign_shl_sat,
474 .assign_shr,
475 .assign_bit_xor,
476 .assign_div,
477 .assign_sub,
478 .assign_sub_wrap,
479 .assign_sub_sat,
480 .assign_mod,
481 .assign_add,
482 .assign_add_wrap,
483 .assign_add_sat,
484 .assign_mul,
485 .assign_mul_wrap,
486 .assign_mul_sat,
487 => {
488 const lhs, const rhs = tree.nodeData(node).node_and_node;
489 try renderExpression(r, lhs, .space);
490 const op_token = tree.nodeMainToken(node);
491 try ais.pushIndent(.after_equals);
492 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
493 try renderToken(r, op_token, .space);
494 } else {
495 try renderToken(r, op_token, .newline);
496 }
497 try renderExpression(r, rhs, space);
498 ais.popIndent();
499 },
500
501 .add,
502 .add_wrap,
503 .add_sat,
504 .array_cat,
505 .array_mult,
506 .bang_equal,
507 .bit_and,
508 .bit_or,
509 .shl,
510 .shl_sat,
511 .shr,
512 .bit_xor,
513 .bool_and,
514 .bool_or,
515 .div,
516 .equal_equal,
517 .greater_or_equal,
518 .greater_than,
519 .less_or_equal,
520 .less_than,
521 .merge_error_sets,
522 .mod,
523 .mul,
524 .mul_wrap,
525 .mul_sat,
526 .sub,
527 .sub_wrap,
528 .sub_sat,
529 .@"orelse",
530 => {
531 const lhs, const rhs = tree.nodeData(node).node_and_node;
532 try renderExpression(r, lhs, .space);
533 const op_token = tree.nodeMainToken(node);
534 try ais.pushIndent(.binop);
535 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
536 try renderToken(r, op_token, .space);
537 } else {
538 try renderToken(r, op_token, .newline);
539 }
540 try renderExpression(r, rhs, space);
541 ais.popIndent();
542 },
543
544 .assign_destructure => {
545 const full = tree.assignDestructure(node);
546 if (full.comptime_token) |comptime_token| {
547 try renderToken(r, comptime_token, .space);
548 }
549
550 for (full.ast.variables, 0..) |variable_node, i| {
551 const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space;
552 switch (tree.nodeTag(variable_node)) {
553 .global_var_decl,
554 .local_var_decl,
555 .simple_var_decl,
556 .aligned_var_decl,
557 => {
558 try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
559 },
560 else => try renderExpression(r, variable_node, variable_space),
561 }
562 }
563 try ais.pushIndent(.after_equals);
564 if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
565 try renderToken(r, full.ast.equal_token, .space);
566 } else {
567 try renderToken(r, full.ast.equal_token, .newline);
568 }
569 try renderExpression(r, full.ast.value_expr, space);
570 ais.popIndent();
571 },
572
573 .bit_not,
574 .bool_not,
575 .negation,
576 .negation_wrap,
577 .optional_type,
578 .address_of,
579 => {
580 try renderToken(r, tree.nodeMainToken(node), .none);
581 return renderExpression(r, tree.nodeData(node).node, space);
582 },
583
584 .@"try",
585 .@"resume",
586 => {
587 try renderToken(r, tree.nodeMainToken(node), .space);
588 return renderExpression(r, tree.nodeData(node).node, space);
589 },
590
591 .array_type,
592 .array_type_sentinel,
593 => return renderArrayType(r, tree.fullArrayType(node).?, space),
594
595 .ptr_type_aligned,
596 .ptr_type_sentinel,
597 .ptr_type,
598 .ptr_type_bit_range,
599 => return renderPtrType(r, tree.fullPtrType(node).?, space),
600
601 .array_init_one,
602 .array_init_one_comma,
603 .array_init_dot_two,
604 .array_init_dot_two_comma,
605 .array_init_dot,
606 .array_init_dot_comma,
607 .array_init,
608 .array_init_comma,
609 => {
610 var elements: [2]Ast.Node.Index = undefined;
611 return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
612 },
613
614 .struct_init_one,
615 .struct_init_one_comma,
616 .struct_init_dot_two,
617 .struct_init_dot_two_comma,
618 .struct_init_dot,
619 .struct_init_dot_comma,
620 .struct_init,
621 .struct_init_comma,
622 => {
623 var buf: [2]Ast.Node.Index = undefined;
624 return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
625 },
626
627 .call_one,
628 .call_one_comma,
629 .call,
630 .call_comma,
631 => {
632 var buf: [1]Ast.Node.Index = undefined;
633 return renderCall(r, tree.fullCall(&buf, node).?, space);
634 },
635
636 .array_access => {
637 const lhs, const rhs = tree.nodeData(node).node_and_node;
638 const lbracket = tree.firstToken(rhs) - 1;
639 const rbracket = tree.lastToken(rhs) + 1;
640 const one_line = tree.tokensOnSameLine(lbracket, rbracket);
641 const inner_space = if (one_line) Space.none else Space.newline;
642 try renderExpression(r, lhs, .none);
643 try ais.pushIndent(.normal);
644 try renderToken(r, lbracket, inner_space); // [
645 try renderExpression(r, rhs, inner_space);
646 ais.popIndent();
647 return renderToken(r, rbracket, space); // ]
648 },
649
650 .slice_open,
651 .slice,
652 .slice_sentinel,
653 => return renderSlice(r, node, tree.fullSlice(node).?, space),
654
655 .deref => {
656 try renderExpression(r, tree.nodeData(node).node, .none);
657 return renderToken(r, tree.nodeMainToken(node), space);
658 },
659
660 .unwrap_optional => {
661 const lhs, const question_mark = tree.nodeData(node).node_and_token;
662 const dot_token = question_mark - 1;
663 try renderExpression(r, lhs, .none);
664 try renderToken(r, dot_token, .none);
665 return renderToken(r, question_mark, space);
666 },
667
668 .@"break", .@"continue" => {
669 const main_token = tree.nodeMainToken(node);
670 const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
671 if (opt_label_token == .none and opt_target == .none) {
672 try renderToken(r, main_token, space); // break/continue
673 } else if (opt_label_token == .none and opt_target != .none) {
674 const target = opt_target.unwrap().?;
675 try renderToken(r, main_token, .space); // break/continue
676 try renderExpression(r, target, space);
677 } else if (opt_label_token != .none and opt_target == .none) {
678 const label_token = opt_label_token.unwrap().?;
679 try renderToken(r, main_token, .space); // break/continue
680 try renderToken(r, label_token - 1, .none); // :
681 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
682 } else if (opt_label_token != .none and opt_target != .none) {
683 const label_token = opt_label_token.unwrap().?;
684 const target = opt_target.unwrap().?;
685 try renderToken(r, main_token, .space); // break/continue
686 try renderToken(r, label_token - 1, .none); // :
687 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
688 try renderExpression(r, target, space);
689 } else unreachable;
690 },
691
692 .@"return" => {
693 if (tree.nodeData(node).opt_node.unwrap()) |expr| {
694 try renderToken(r, tree.nodeMainToken(node), .space);
695 try renderExpression(r, expr, space);
696 } else {
697 try renderToken(r, tree.nodeMainToken(node), space);
698 }
699 },
700
701 .grouped_expression => {
702 const expr, const rparen = tree.nodeData(node).node_and_token;
703 try ais.pushIndent(.normal);
704 try renderToken(r, tree.nodeMainToken(node), .none); // lparen
705 try renderExpression(r, expr, .none);
706 ais.popIndent();
707 return renderToken(r, rparen, space);
708 },
709
710 .container_decl,
711 .container_decl_trailing,
712 .container_decl_arg,
713 .container_decl_arg_trailing,
714 .container_decl_two,
715 .container_decl_two_trailing,
716 .tagged_union,
717 .tagged_union_trailing,
718 .tagged_union_enum_tag,
719 .tagged_union_enum_tag_trailing,
720 .tagged_union_two,
721 .tagged_union_two_trailing,
722 => {
723 var buf: [2]Ast.Node.Index = undefined;
724 return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
725 },
726
727 .error_set_decl => {
728 const error_token = tree.nodeMainToken(node);
729 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
730
731 try renderToken(r, error_token, .none);
732
733 if (lbrace + 1 == rbrace) {
734 // There is nothing between the braces so render condensed: `error{}`
735 try renderToken(r, lbrace, .none);
736 return renderToken(r, rbrace, space);
737 } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
738 // There is exactly one member and no trailing comma or
739 // comments, so render without surrounding spaces: `error{Foo}`
740 try renderToken(r, lbrace, .none);
741 try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
742 return renderToken(r, rbrace, space);
743 } else if (tree.tokenTag(rbrace - 1) == .comma) {
744 // There is a trailing comma so render each member on a new line.
745 try ais.pushIndent(.normal);
746 try renderToken(r, lbrace, .newline);
747 var i = lbrace + 1;
748 while (i < rbrace) : (i += 1) {
749 if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
750 switch (tree.tokenTag(i)) {
751 .doc_comment => try renderToken(r, i, .newline),
752 .identifier => {
753 try ais.pushSpace(.comma);
754 try renderIdentifier(r, i, .comma, .eagerly_unquote);
755 ais.popSpace();
756 },
757 .comma => {},
758 else => unreachable,
759 }
760 }
761 ais.popIndent();
762 return renderToken(r, rbrace, space);
763 } else {
764 // There is no trailing comma so render everything on one line.
765 try renderToken(r, lbrace, .space);
766 var i = lbrace + 1;
767 while (i < rbrace) : (i += 1) {
768 switch (tree.tokenTag(i)) {
769 .doc_comment => unreachable, // TODO
770 .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
771 .comma => {},
772 else => unreachable,
773 }
774 }
775 return renderToken(r, rbrace, space);
776 }
777 },
778
779 .builtin_call_two,
780 .builtin_call_two_comma,
781 .builtin_call,
782 .builtin_call_comma,
783 => {
784 var buf: [2]Ast.Node.Index = undefined;
785 const params = tree.builtinCallParams(&buf, node).?;
786 return renderBuiltinCall(r, tree.nodeMainToken(node), params, space);
787 },
788
789 .fn_proto_simple,
790 .fn_proto_multi,
791 .fn_proto_one,
792 .fn_proto,
793 => {
794 var buf: [1]Ast.Node.Index = undefined;
795 return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
796 },
797
798 .anyframe_type => {
799 const main_token = tree.nodeMainToken(node);
800 try renderToken(r, main_token, .none); // anyframe
801 try renderToken(r, main_token + 1, .none); // ->
802 return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
803 },
804
805 .@"switch",
806 .switch_comma,
807 => {
808 const full = tree.switchFull(node);
809
810 if (full.label_token) |label_token| {
811 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
812 try renderToken(r, label_token + 1, .space); // :
813 }
814
815 const rparen = tree.lastToken(full.ast.condition) + 1;
816
817 try renderToken(r, full.ast.switch_token, .space); // switch
818 try renderToken(r, full.ast.switch_token + 1, .none); // (
819 try renderExpression(r, full.ast.condition, .none); // condition expression
820 try renderToken(r, rparen, .space); // )
821
822 try ais.pushIndent(.normal);
823 if (full.ast.cases.len == 0) {
824 try renderToken(r, rparen + 1, .none); // {
825 } else {
826 try renderToken(r, rparen + 1, .newline); // {
827 try ais.pushSpace(.comma);
828 try renderExpressions(r, full.ast.cases, .comma);
829 ais.popSpace();
830 }
831 ais.popIndent();
832 return renderToken(r, tree.lastToken(node), space); // }
833 },
834
835 .switch_case_one,
836 .switch_case_inline_one,
837 .switch_case,
838 .switch_case_inline,
839 => return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
840
841 .while_simple,
842 .while_cont,
843 .@"while",
844 => return renderWhile(r, tree.fullWhile(node).?, space),
845
846 .for_simple,
847 .@"for",
848 => return renderFor(r, tree.fullFor(node).?, space),
849
850 .if_simple,
851 .@"if",
852 => return renderIf(r, tree.fullIf(node).?, space),
853
854 .asm_simple,
855 .@"asm",
856 => return renderAsm(r, tree.fullAsm(node).?, space),
857
858 // To be removed after 0.15.0 is tagged
859 .asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),
860
861 .enum_literal => {
862 try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
863 return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
864 },
865
866 .fn_decl => unreachable,
867 .container_field => unreachable,
868 .container_field_init => unreachable,
869 .container_field_align => unreachable,
870 .root => unreachable,
871 .global_var_decl => unreachable,
872 .local_var_decl => unreachable,
873 .simple_var_decl => unreachable,
874 .aligned_var_decl => unreachable,
875 .test_decl => unreachable,
876 .asm_output => unreachable,
877 .asm_input => unreachable,
878 }
879}
880
881/// Same as `renderExpression`, but afterwards looks for any
882/// append_string_after_node fixups to apply
883fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
884 const ais = r.ais;
885 try renderExpression(r, node, space);
886 if (r.fixups.append_string_after_node.get(node)) |bytes| {
887 try ais.writeAll(bytes);
888 }
889}
890
891fn renderArrayType(
892 r: *Render,
893 array_type: Ast.full.ArrayType,
894 space: Space,
895) Error!void {
896 const tree = r.tree;
897 const ais = r.ais;
898 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
899 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket);
900 const inner_space = if (one_line) Space.none else Space.newline;
901 try ais.pushIndent(.normal);
902 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
903 try renderExpression(r, array_type.ast.elem_count, inner_space);
904 if (array_type.ast.sentinel.unwrap()) |sentinel| {
905 try renderToken(r, tree.firstToken(sentinel) - 1, inner_space); // colon
906 try renderExpression(r, sentinel, inner_space);
907 }
908 ais.popIndent();
909 try renderToken(r, rbracket, .none); // rbracket
910 return renderExpression(r, array_type.ast.elem_type, space);
911}
912
913fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
914 const tree = r.tree;
915 const main_token = ptr_type.ast.main_token;
916 switch (ptr_type.size) {
917 .one => {
918 // Since ** tokens exist and the same token is shared by two
919 // nested pointer types, we check to see if we are the parent
920 // in such a relationship. If so, skip rendering anything for
921 // this pointer type and rely on the child to render our asterisk
922 // as well when it renders the ** token.
923 if (tree.tokenTag(main_token) == .asterisk_asterisk and
924 main_token == tree.nodeMainToken(ptr_type.ast.child_type))
925 {
926 return renderExpression(r, ptr_type.ast.child_type, space);
927 }
928 try renderToken(r, main_token, .none); // asterisk
929 },
930 .many => {
931 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
932 try renderToken(r, main_token, .none); // lbracket
933 try renderToken(r, main_token + 1, .none); // asterisk
934 try renderToken(r, main_token + 2, .none); // colon
935 try renderExpression(r, sentinel, .none);
936 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
937 } else {
938 try renderToken(r, main_token, .none); // lbracket
939 try renderToken(r, main_token + 1, .none); // asterisk
940 try renderToken(r, main_token + 2, .none); // rbracket
941 }
942 },
943 .c => {
944 try renderToken(r, main_token, .none); // lbracket
945 try renderToken(r, main_token + 1, .none); // asterisk
946 try renderToken(r, main_token + 2, .none); // c
947 try renderToken(r, main_token + 3, .none); // rbracket
948 },
949 .slice => {
950 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
951 try renderToken(r, main_token, .none); // lbracket
952 try renderToken(r, main_token + 1, .none); // colon
953 try renderExpression(r, sentinel, .none);
954 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
955 } else {
956 try renderToken(r, main_token, .none); // lbracket
957 try renderToken(r, main_token + 1, .none); // rbracket
958 }
959 },
960 }
961
962 if (ptr_type.allowzero_token) |allowzero_token| {
963 try renderToken(r, allowzero_token, .space);
964 }
965
966 if (ptr_type.ast.align_node.unwrap()) |align_node| {
967 const align_first = tree.firstToken(align_node);
968 try renderToken(r, align_first - 2, .none); // align
969 try renderToken(r, align_first - 1, .none); // lparen
970 try renderExpression(r, align_node, .none);
971 if (ptr_type.ast.bit_range_start.unwrap()) |bit_range_start| {
972 const bit_range_end = ptr_type.ast.bit_range_end.unwrap().?;
973 try renderToken(r, tree.firstToken(bit_range_start) - 1, .none); // colon
974 try renderExpression(r, bit_range_start, .none);
975 try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon
976 try renderExpression(r, bit_range_end, .none);
977 try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen
978 } else {
979 try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen
980 }
981 }
982
983 if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| {
984 const addrspace_first = tree.firstToken(addrspace_node);
985 try renderToken(r, addrspace_first - 2, .none); // addrspace
986 try renderToken(r, addrspace_first - 1, .none); // lparen
987 try renderExpression(r, addrspace_node, .none);
988 try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen
989 }
990
991 if (ptr_type.const_token) |const_token| {
992 try renderToken(r, const_token, .space);
993 }
994
995 if (ptr_type.volatile_token) |volatile_token| {
996 try renderToken(r, volatile_token, .space);
997 }
998
999 try renderExpression(r, ptr_type.ast.child_type, space);
1000}
1001
1002fn renderSlice(
1003 r: *Render,
1004 slice_node: Ast.Node.Index,
1005 slice: Ast.full.Slice,
1006 space: Space,
1007) Error!void {
1008 const tree = r.tree;
1009 const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or
1010 if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false;
1011 const after_start_space = if (after_start_space_bool) Space.space else Space.none;
1012 const after_dots_space = if (slice.ast.end != .none)
1013 after_start_space
1014 else if (slice.ast.sentinel != .none) Space.space else Space.none;
1015
1016 try renderExpression(r, slice.ast.sliced, .none);
1017 try renderToken(r, slice.ast.lbracket, .none); // lbracket
1018
1019 const start_last = tree.lastToken(slice.ast.start);
1020 try renderExpression(r, slice.ast.start, after_start_space);
1021 try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..")
1022
1023 if (slice.ast.end.unwrap()) |end| {
1024 const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none;
1025 try renderExpression(r, end, after_end_space);
1026 }
1027
1028 if (slice.ast.sentinel.unwrap()) |sentinel| {
1029 try renderToken(r, tree.firstToken(sentinel) - 1, .none); // colon
1030 try renderExpression(r, sentinel, .none);
1031 }
1032
1033 try renderToken(r, tree.lastToken(slice_node), space); // rbracket
1034}
1035
1036fn renderAsmOutput(
1037 r: *Render,
1038 asm_output: Ast.Node.Index,
1039 space: Space,
1040) Error!void {
1041 const tree = r.tree;
1042 assert(tree.nodeTag(asm_output) == .asm_output);
1043 const symbolic_name = tree.nodeMainToken(asm_output);
1044
1045 try renderToken(r, symbolic_name - 1, .none); // lbracket
1046 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1047 try renderToken(r, symbolic_name + 1, .space); // rbracket
1048 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1049 try renderToken(r, symbolic_name + 3, .none); // lparen
1050
1051 if (tree.tokenTag(symbolic_name + 4) == .arrow) {
1052 const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token;
1053 try renderToken(r, symbolic_name + 4, .space); // ->
1054 try renderExpression(r, type_expr.unwrap().?, Space.none);
1055 return renderToken(r, rparen, space);
1056 } else {
1057 try renderIdentifier(r, symbolic_name + 4, .none, .eagerly_unquote); // ident
1058 return renderToken(r, symbolic_name + 5, space); // rparen
1059 }
1060}
1061
1062fn renderAsmInput(
1063 r: *Render,
1064 asm_input: Ast.Node.Index,
1065 space: Space,
1066) Error!void {
1067 const tree = r.tree;
1068 assert(tree.nodeTag(asm_input) == .asm_input);
1069 const symbolic_name = tree.nodeMainToken(asm_input);
1070 const expr, const rparen = tree.nodeData(asm_input).node_and_token;
1071
1072 try renderToken(r, symbolic_name - 1, .none); // lbracket
1073 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1074 try renderToken(r, symbolic_name + 1, .space); // rbracket
1075 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1076 try renderToken(r, symbolic_name + 3, .none); // lparen
1077 try renderExpression(r, expr, Space.none);
1078 return renderToken(r, rparen, space);
1079}
1080
1081fn renderVarDecl(
1082 r: *Render,
1083 var_decl: Ast.full.VarDecl,
1084 /// Destructures intentionally ignore leading `comptime` tokens.
1085 ignore_comptime_token: bool,
1086 /// `comma_space` and `space` are used for destructure LHS decls.
1087 space: Space,
1088) Error!void {
1089 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1090 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
1091 // Discard the variable like this: `_ = foo;`
1092 const ais = r.ais;
1093 try ais.writeAll("_ = ");
1094 try ais.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1095 try ais.writeAll(";\n");
1096 }
1097}
1098
1099fn renderVarDeclWithoutFixups(
1100 r: *Render,
1101 var_decl: Ast.full.VarDecl,
1102 /// Destructures intentionally ignore leading `comptime` tokens.
1103 ignore_comptime_token: bool,
1104 /// `comma_space` and `space` are used for destructure LHS decls.
1105 space: Space,
1106) Error!void {
1107 const tree = r.tree;
1108 const ais = r.ais;
1109
1110 if (var_decl.visib_token) |visib_token| {
1111 try renderToken(r, visib_token, Space.space); // pub
1112 }
1113
1114 if (var_decl.extern_export_token) |extern_export_token| {
1115 try renderToken(r, extern_export_token, Space.space); // extern
1116
1117 if (var_decl.lib_name) |lib_name| {
1118 try renderToken(r, lib_name, Space.space); // "lib"
1119 }
1120 }
1121
1122 if (var_decl.threadlocal_token) |thread_local_token| {
1123 try renderToken(r, thread_local_token, Space.space); // threadlocal
1124 }
1125
1126 if (!ignore_comptime_token) {
1127 if (var_decl.comptime_token) |comptime_token| {
1128 try renderToken(r, comptime_token, Space.space); // comptime
1129 }
1130 }
1131
1132 try renderToken(r, var_decl.ast.mut_token, .space); // var
1133
1134 if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or
1135 var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1136 var_decl.ast.init_node != .none)
1137 {
1138 const name_space = if (var_decl.ast.type_node == .none and
1139 (var_decl.ast.align_node != .none or
1140 var_decl.ast.addrspace_node != .none or
1141 var_decl.ast.section_node != .none or
1142 var_decl.ast.init_node != .none))
1143 Space.space
1144 else
1145 Space.none;
1146
1147 try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name
1148 } else {
1149 return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name
1150 }
1151
1152 if (var_decl.ast.type_node.unwrap()) |type_node| {
1153 try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // :
1154 if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or
1155 var_decl.ast.section_node != .none or var_decl.ast.init_node != .none)
1156 {
1157 try renderExpression(r, type_node, .space);
1158 } else {
1159 return renderExpression(r, type_node, space);
1160 }
1161 }
1162
1163 if (var_decl.ast.align_node.unwrap()) |align_node| {
1164 const lparen = tree.firstToken(align_node) - 1;
1165 const align_kw = lparen - 1;
1166 const rparen = tree.lastToken(align_node) + 1;
1167 try renderToken(r, align_kw, Space.none); // align
1168 try renderToken(r, lparen, Space.none); // (
1169 try renderExpression(r, align_node, Space.none);
1170 if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1171 var_decl.ast.init_node != .none)
1172 {
1173 try renderToken(r, rparen, .space); // )
1174 } else {
1175 return renderToken(r, rparen, space); // )
1176 }
1177 }
1178
1179 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
1180 const lparen = tree.firstToken(addrspace_node) - 1;
1181 const addrspace_kw = lparen - 1;
1182 const rparen = tree.lastToken(addrspace_node) + 1;
1183 try renderToken(r, addrspace_kw, Space.none); // addrspace
1184 try renderToken(r, lparen, Space.none); // (
1185 try renderExpression(r, addrspace_node, Space.none);
1186 if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) {
1187 try renderToken(r, rparen, .space); // )
1188 } else {
1189 try renderToken(r, rparen, .none); // )
1190 return renderToken(r, rparen + 1, Space.newline); // ;
1191 }
1192 }
1193
1194 if (var_decl.ast.section_node.unwrap()) |section_node| {
1195 const lparen = tree.firstToken(section_node) - 1;
1196 const section_kw = lparen - 1;
1197 const rparen = tree.lastToken(section_node) + 1;
1198 try renderToken(r, section_kw, Space.none); // linksection
1199 try renderToken(r, lparen, Space.none); // (
1200 try renderExpression(r, section_node, Space.none);
1201 if (var_decl.ast.init_node != .none) {
1202 try renderToken(r, rparen, .space); // )
1203 } else {
1204 return renderToken(r, rparen, space); // )
1205 }
1206 }
1207
1208 const init_node = var_decl.ast.init_node.unwrap().?;
1209
1210 const eq_token = tree.firstToken(init_node) - 1;
1211 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1212 try ais.pushIndent(.after_equals);
1213 try renderToken(r, eq_token, eq_space); // =
1214 try renderExpression(r, init_node, space); // ;
1215 ais.popIndent();
1216}
1217
1218fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
1219 return renderWhile(r, .{
1220 .ast = .{
1221 .while_token = if_node.ast.if_token,
1222 .cond_expr = if_node.ast.cond_expr,
1223 .cont_expr = .none,
1224 .then_expr = if_node.ast.then_expr,
1225 .else_expr = if_node.ast.else_expr,
1226 },
1227 .inline_token = null,
1228 .label_token = null,
1229 .payload_token = if_node.payload_token,
1230 .else_token = if_node.else_token,
1231 .error_token = if_node.error_token,
1232 }, space);
1233}
1234
1235/// Note that this function is additionally used to render if expressions, with
1236/// respective values set to null.
1237fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
1238 const tree = r.tree;
1239
1240 if (while_node.label_token) |label| {
1241 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1242 try renderToken(r, label + 1, .space); // :
1243 }
1244
1245 if (while_node.inline_token) |inline_token| {
1246 try renderToken(r, inline_token, .space); // inline
1247 }
1248
1249 try renderToken(r, while_node.ast.while_token, .space); // if/for/while
1250 try renderToken(r, while_node.ast.while_token + 1, .none); // lparen
1251 try renderExpression(r, while_node.ast.cond_expr, .none); // condition
1252
1253 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
1254
1255 if (while_node.payload_token) |payload_token| {
1256 try renderToken(r, last_prefix_token, .space);
1257 try renderToken(r, payload_token - 1, .none); // |
1258 const ident = blk: {
1259 if (tree.tokenTag(payload_token) == .asterisk) {
1260 try renderToken(r, payload_token, .none); // *
1261 break :blk payload_token + 1;
1262 } else {
1263 break :blk payload_token;
1264 }
1265 };
1266 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1267 const pipe = blk: {
1268 if (tree.tokenTag(ident + 1) == .comma) {
1269 try renderToken(r, ident + 1, .space); // ,
1270 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // index
1271 break :blk ident + 3;
1272 } else {
1273 break :blk ident + 1;
1274 }
1275 };
1276 last_prefix_token = pipe;
1277 }
1278
1279 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
1280 try renderToken(r, last_prefix_token, .space);
1281 const lparen = tree.firstToken(cont_expr) - 1;
1282 try renderToken(r, lparen - 1, .space); // :
1283 try renderToken(r, lparen, .none); // lparen
1284 try renderExpression(r, cont_expr, .none);
1285 last_prefix_token = tree.lastToken(cont_expr) + 1; // rparen
1286 }
1287
1288 try renderThenElse(
1289 r,
1290 last_prefix_token,
1291 while_node.ast.then_expr,
1292 while_node.else_token,
1293 while_node.error_token,
1294 while_node.ast.else_expr,
1295 space,
1296 );
1297}
1298
1299fn renderThenElse(
1300 r: *Render,
1301 last_prefix_token: Ast.TokenIndex,
1302 then_expr: Ast.Node.Index,
1303 else_token: ?Ast.TokenIndex,
1304 maybe_error_token: ?Ast.TokenIndex,
1305 opt_else_expr: Ast.Node.OptionalIndex,
1306 space: Space,
1307) Error!void {
1308 const tree = r.tree;
1309 const ais = r.ais;
1310 const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr));
1311 const indent_then_expr = !then_expr_is_block and
1312 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr));
1313
1314 if (indent_then_expr) try ais.pushIndent(.normal);
1315
1316 if (then_expr_is_block and ais.isLineOverIndented()) {
1317 ais.disableIndentCommitting();
1318 try renderToken(r, last_prefix_token, .newline);
1319 ais.enableIndentCommitting();
1320 } else if (indent_then_expr) {
1321 try renderToken(r, last_prefix_token, .newline);
1322 } else {
1323 try renderToken(r, last_prefix_token, .space);
1324 }
1325
1326 if (opt_else_expr.unwrap()) |else_expr| {
1327 if (indent_then_expr) {
1328 try renderExpression(r, then_expr, .newline);
1329 } else {
1330 try renderExpression(r, then_expr, .space);
1331 }
1332
1333 if (indent_then_expr) ais.popIndent();
1334
1335 var last_else_token = else_token.?;
1336
1337 if (maybe_error_token) |error_token| {
1338 try renderToken(r, last_else_token, .space); // else
1339 try renderToken(r, error_token - 1, .none); // |
1340 try renderIdentifier(r, error_token, .none, .preserve_when_shadowing); // identifier
1341 last_else_token = error_token + 1; // |
1342 }
1343
1344 const indent_else_expr = indent_then_expr and
1345 !nodeIsBlock(tree.nodeTag(else_expr)) and
1346 !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr));
1347 if (indent_else_expr) {
1348 try ais.pushIndent(.normal);
1349 try renderToken(r, last_else_token, .newline);
1350 try renderExpression(r, else_expr, space);
1351 ais.popIndent();
1352 } else {
1353 try renderToken(r, last_else_token, .space);
1354 try renderExpression(r, else_expr, space);
1355 }
1356 } else {
1357 try renderExpression(r, then_expr, space);
1358 if (indent_then_expr) ais.popIndent();
1359 }
1360}
1361
1362fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
1363 const tree = r.tree;
1364 const ais = r.ais;
1365 const token_tags = tree.tokens.items(.tag);
1366
1367 if (for_node.label_token) |label| {
1368 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1369 try renderToken(r, label + 1, .space); // :
1370 }
1371
1372 if (for_node.inline_token) |inline_token| {
1373 try renderToken(r, inline_token, .space); // inline
1374 }
1375
1376 try renderToken(r, for_node.ast.for_token, .space); // if/for/while
1377
1378 const lparen = for_node.ast.for_token + 1;
1379 try renderParamList(r, lparen, for_node.ast.inputs, .space);
1380
1381 var cur = for_node.payload_token;
1382 const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?;
1383 if (tree.tokenTag(@intCast(pipe - 1)) == .comma) {
1384 try ais.pushIndent(.normal);
1385 try renderToken(r, cur - 1, .newline); // |
1386 while (true) {
1387 if (tree.tokenTag(cur) == .asterisk) {
1388 try renderToken(r, cur, .none); // *
1389 cur += 1;
1390 }
1391 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1392 cur += 1;
1393 if (tree.tokenTag(cur) == .comma) {
1394 try renderToken(r, cur, .newline); // ,
1395 cur += 1;
1396 }
1397 if (tree.tokenTag(cur) == .pipe) {
1398 break;
1399 }
1400 }
1401 ais.popIndent();
1402 } else {
1403 try renderToken(r, cur - 1, .none); // |
1404 while (true) {
1405 if (tree.tokenTag(cur) == .asterisk) {
1406 try renderToken(r, cur, .none); // *
1407 cur += 1;
1408 }
1409 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1410 cur += 1;
1411 if (tree.tokenTag(cur) == .comma) {
1412 try renderToken(r, cur, .space); // ,
1413 cur += 1;
1414 }
1415 if (tree.tokenTag(cur) == .pipe) {
1416 break;
1417 }
1418 }
1419 }
1420
1421 try renderThenElse(
1422 r,
1423 cur,
1424 for_node.ast.then_expr,
1425 for_node.else_token,
1426 null,
1427 for_node.ast.else_expr,
1428 space,
1429 );
1430}
1431
1432fn renderContainerField(
1433 r: *Render,
1434 container: Container,
1435 field_param: Ast.full.ContainerField,
1436 space: Space,
1437) Error!void {
1438 const tree = r.tree;
1439 const ais = r.ais;
1440 var field = field_param;
1441 if (container != .tuple) field.convertToNonTupleLike(&tree);
1442 const quote: QuoteBehavior = switch (container) {
1443 .@"enum" => .eagerly_unquote_except_underscore,
1444 .tuple, .other => .eagerly_unquote,
1445 };
1446
1447 if (field.comptime_token) |t| {
1448 try renderToken(r, t, .space); // comptime
1449 }
1450 if (field.ast.type_expr == .none and field.ast.value_expr == .none) {
1451 if (field.ast.align_expr.unwrap()) |align_expr| {
1452 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1453 const lparen_token = tree.firstToken(align_expr) - 1;
1454 const align_kw = lparen_token - 1;
1455 const rparen_token = tree.lastToken(align_expr) + 1;
1456 try renderToken(r, align_kw, .none); // align
1457 try renderToken(r, lparen_token, .none); // (
1458 try renderExpression(r, align_expr, .none); // alignment
1459 return renderToken(r, rparen_token, .space); // )
1460 }
1461 return renderIdentifierComma(r, field.ast.main_token, space, quote); // name
1462 }
1463 if (field.ast.type_expr != .none and field.ast.value_expr == .none) {
1464 const type_expr = field.ast.type_expr.unwrap().?;
1465 if (!field.ast.tuple_like) {
1466 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1467 try renderToken(r, field.ast.main_token + 1, .space); // :
1468 }
1469
1470 if (field.ast.align_expr.unwrap()) |align_expr| {
1471 try renderExpression(r, type_expr, .space); // type
1472 const align_token = tree.firstToken(align_expr) - 2;
1473 try renderToken(r, align_token, .none); // align
1474 try renderToken(r, align_token + 1, .none); // (
1475 try renderExpression(r, align_expr, .none); // alignment
1476 const rparen = tree.lastToken(align_expr) + 1;
1477 return renderTokenComma(r, rparen, space); // )
1478 } else {
1479 return renderExpressionComma(r, type_expr, space); // type
1480 }
1481 }
1482 if (field.ast.type_expr == .none and field.ast.value_expr != .none) {
1483 const value_expr = field.ast.value_expr.unwrap().?;
1484
1485 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1486 if (field.ast.align_expr.unwrap()) |align_expr| {
1487 const lparen_token = tree.firstToken(align_expr) - 1;
1488 const align_kw = lparen_token - 1;
1489 const rparen_token = tree.lastToken(align_expr) + 1;
1490 try renderToken(r, align_kw, .none); // align
1491 try renderToken(r, lparen_token, .none); // (
1492 try renderExpression(r, align_expr, .none); // alignment
1493 try renderToken(r, rparen_token, .space); // )
1494 }
1495 try renderToken(r, field.ast.main_token + 1, .space); // =
1496 return renderExpressionComma(r, value_expr, space); // value
1497 }
1498 if (!field.ast.tuple_like) {
1499 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1500 try renderToken(r, field.ast.main_token + 1, .space); // :
1501 }
1502
1503 const type_expr = field.ast.type_expr.unwrap().?;
1504 const value_expr = field.ast.value_expr.unwrap().?;
1505
1506 try renderExpression(r, type_expr, .space); // type
1507
1508 if (field.ast.align_expr.unwrap()) |align_expr| {
1509 const lparen_token = tree.firstToken(align_expr) - 1;
1510 const align_kw = lparen_token - 1;
1511 const rparen_token = tree.lastToken(align_expr) + 1;
1512 try renderToken(r, align_kw, .none); // align
1513 try renderToken(r, lparen_token, .none); // (
1514 try renderExpression(r, align_expr, .none); // alignment
1515 try renderToken(r, rparen_token, .space); // )
1516 }
1517 const eq_token = tree.firstToken(value_expr) - 1;
1518 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1519
1520 try ais.pushIndent(.after_equals);
1521 try renderToken(r, eq_token, eq_space); // =
1522
1523 if (eq_space == .space) {
1524 ais.popIndent();
1525 try renderExpressionComma(r, value_expr, space); // value
1526 return;
1527 }
1528
1529 const maybe_comma = tree.lastToken(value_expr) + 1;
1530
1531 if (tree.tokenTag(maybe_comma) == .comma) {
1532 try renderExpression(r, value_expr, .none); // value
1533 ais.popIndent();
1534 try renderToken(r, maybe_comma, .newline);
1535 } else {
1536 try renderExpression(r, value_expr, space); // value
1537 ais.popIndent();
1538 }
1539}
1540
1541fn renderBuiltinCall(
1542 r: *Render,
1543 builtin_token: Ast.TokenIndex,
1544 params: []const Ast.Node.Index,
1545 space: Space,
1546) Error!void {
1547 const tree = r.tree;
1548 const ais = r.ais;
1549
1550 try renderToken(r, builtin_token, .none); // @name
1551
1552 if (params.len == 0) {
1553 try renderToken(r, builtin_token + 1, .none); // (
1554 return renderToken(r, builtin_token + 2, space); // )
1555 }
1556
1557 if (r.fixups.rebase_imported_paths) |prefix| {
1558 const slice = tree.tokenSlice(builtin_token);
1559 if (mem.eql(u8, slice, "@import")) f: {
1560 const param = params[0];
1561 const str_lit_token = tree.nodeMainToken(param);
1562 assert(tree.tokenTag(str_lit_token) == .string_literal);
1563 const token_bytes = tree.tokenSlice(str_lit_token);
1564 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1565 error.OutOfMemory => return error.OutOfMemory,
1566 error.InvalidLiteral => break :f,
1567 };
1568 defer r.gpa.free(imported_string);
1569 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1570 defer r.gpa.free(new_string);
1571
1572 try renderToken(r, builtin_token + 1, .none); // (
1573 try ais.print("\"{f}\"", .{std.zig.fmtString(new_string)});
1574 return renderToken(r, str_lit_token + 1, space); // )
1575 }
1576 }
1577
1578 const last_param = params[params.len - 1];
1579 const after_last_param_token = tree.lastToken(last_param) + 1;
1580
1581 if (tree.tokenTag(after_last_param_token) != .comma) {
1582 // Render all on one line, no trailing comma.
1583 try renderToken(r, builtin_token + 1, .none); // (
1584
1585 for (params, 0..) |param_node, i| {
1586 const first_param_token = tree.firstToken(param_node);
1587 if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or
1588 hasSameLineComment(tree, first_param_token - 1))
1589 {
1590 try ais.pushIndent(.normal);
1591 try renderExpression(r, param_node, .none);
1592 ais.popIndent();
1593 } else {
1594 try renderExpression(r, param_node, .none);
1595 }
1596
1597 if (i + 1 < params.len) {
1598 const comma_token = tree.lastToken(param_node) + 1;
1599 try renderToken(r, comma_token, .space); // ,
1600 }
1601 }
1602 return renderToken(r, after_last_param_token, space); // )
1603 } else {
1604 // Render one param per line.
1605 try ais.pushIndent(.normal);
1606 try renderToken(r, builtin_token + 1, Space.newline); // (
1607
1608 for (params) |param_node| {
1609 try ais.pushSpace(.comma);
1610 try renderExpression(r, param_node, .comma);
1611 ais.popSpace();
1612 }
1613 ais.popIndent();
1614
1615 return renderToken(r, after_last_param_token + 1, space); // )
1616 }
1617}
1618
1619fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1620 const tree = r.tree;
1621 const ais = r.ais;
1622
1623 const after_fn_token = fn_proto.ast.fn_token + 1;
1624 const lparen = if (tree.tokenTag(after_fn_token) == .identifier) blk: {
1625 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1626 try renderIdentifier(r, after_fn_token, .none, .preserve_when_shadowing); // name
1627 break :blk after_fn_token + 1;
1628 } else blk: {
1629 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1630 break :blk fn_proto.ast.fn_token + 1;
1631 };
1632 assert(tree.tokenTag(lparen) == .l_paren);
1633
1634 const return_type = fn_proto.ast.return_type.unwrap().?;
1635 const maybe_bang = tree.firstToken(return_type) - 1;
1636 const rparen = blk: {
1637 // These may appear in any order, so we have to check the token_starts array
1638 // to find out which is first.
1639 var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang;
1640 var smallest_start = tree.tokenStart(maybe_bang);
1641 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1642 const tok = tree.firstToken(align_expr) - 3;
1643 const start = tree.tokenStart(tok);
1644 if (start < smallest_start) {
1645 rparen = tok;
1646 smallest_start = start;
1647 }
1648 }
1649 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1650 const tok = tree.firstToken(addrspace_expr) - 3;
1651 const start = tree.tokenStart(tok);
1652 if (start < smallest_start) {
1653 rparen = tok;
1654 smallest_start = start;
1655 }
1656 }
1657 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1658 const tok = tree.firstToken(section_expr) - 3;
1659 const start = tree.tokenStart(tok);
1660 if (start < smallest_start) {
1661 rparen = tok;
1662 smallest_start = start;
1663 }
1664 }
1665 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1666 const tok = tree.firstToken(callconv_expr) - 3;
1667 const start = tree.tokenStart(tok);
1668 if (start < smallest_start) {
1669 rparen = tok;
1670 smallest_start = start;
1671 }
1672 }
1673 break :blk rparen;
1674 };
1675 assert(tree.tokenTag(rparen) == .r_paren);
1676
1677 // The params list is a sparse set that does *not* include anytype or ... parameters.
1678
1679 const trailing_comma = tree.tokenTag(rparen - 1) == .comma;
1680 if (!trailing_comma and !hasComment(tree, lparen, rparen)) {
1681 // Render all on one line, no trailing comma.
1682 try renderToken(r, lparen, .none); // (
1683
1684 var param_i: usize = 0;
1685 var last_param_token = lparen;
1686 while (true) {
1687 last_param_token += 1;
1688 switch (tree.tokenTag(last_param_token)) {
1689 .doc_comment => {
1690 try renderToken(r, last_param_token, .newline);
1691 continue;
1692 },
1693 .ellipsis3 => {
1694 try renderToken(r, last_param_token, .none); // ...
1695 break;
1696 },
1697 .keyword_noalias, .keyword_comptime => {
1698 try renderToken(r, last_param_token, .space);
1699 last_param_token += 1;
1700 },
1701 .identifier => {},
1702 .keyword_anytype => {
1703 try renderToken(r, last_param_token, .none); // anytype
1704 continue;
1705 },
1706 .r_paren => break,
1707 .comma => {
1708 try renderToken(r, last_param_token, .space); // ,
1709 continue;
1710 },
1711 else => {}, // Parameter type without a name.
1712 }
1713 if (tree.tokenTag(last_param_token) == .identifier and
1714 tree.tokenTag(last_param_token + 1) == .colon)
1715 {
1716 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1717 last_param_token = last_param_token + 1;
1718 try renderToken(r, last_param_token, .space); // :
1719 last_param_token += 1;
1720 }
1721 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1722 try renderToken(r, last_param_token, .none); // anytype
1723 continue;
1724 }
1725 const param = fn_proto.ast.params[param_i];
1726 param_i += 1;
1727 try renderExpression(r, param, .none);
1728 last_param_token = tree.lastToken(param);
1729 }
1730 } else {
1731 // One param per line.
1732 try ais.pushIndent(.normal);
1733 try renderToken(r, lparen, .newline); // (
1734
1735 var param_i: usize = 0;
1736 var last_param_token = lparen;
1737 while (true) {
1738 last_param_token += 1;
1739 switch (tree.tokenTag(last_param_token)) {
1740 .doc_comment => {
1741 try renderToken(r, last_param_token, .newline);
1742 continue;
1743 },
1744 .ellipsis3 => {
1745 try renderToken(r, last_param_token, .comma); // ...
1746 break;
1747 },
1748 .keyword_noalias, .keyword_comptime => {
1749 try renderToken(r, last_param_token, .space);
1750 last_param_token += 1;
1751 },
1752 .identifier => {},
1753 .keyword_anytype => {
1754 try renderToken(r, last_param_token, .comma); // anytype
1755 if (tree.tokenTag(last_param_token + 1) == .comma)
1756 last_param_token += 1;
1757 continue;
1758 },
1759 .r_paren => break,
1760 else => {}, // Parameter type without a name.
1761 }
1762 if (tree.tokenTag(last_param_token) == .identifier and
1763 tree.tokenTag(last_param_token + 1) == .colon)
1764 {
1765 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1766 last_param_token += 1;
1767 try renderToken(r, last_param_token, .space); // :
1768 last_param_token += 1;
1769 }
1770 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1771 try renderToken(r, last_param_token, .comma); // anytype
1772 if (tree.tokenTag(last_param_token + 1) == .comma)
1773 last_param_token += 1;
1774 continue;
1775 }
1776 const param = fn_proto.ast.params[param_i];
1777 param_i += 1;
1778 try ais.pushSpace(.comma);
1779 try renderExpression(r, param, .comma);
1780 ais.popSpace();
1781 last_param_token = tree.lastToken(param);
1782 if (tree.tokenTag(last_param_token + 1) == .comma) last_param_token += 1;
1783 }
1784 ais.popIndent();
1785 }
1786
1787 try renderToken(r, rparen, .space); // )
1788
1789 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1790 const align_lparen = tree.firstToken(align_expr) - 1;
1791 const align_rparen = tree.lastToken(align_expr) + 1;
1792
1793 try renderToken(r, align_lparen - 1, .none); // align
1794 try renderToken(r, align_lparen, .none); // (
1795 try renderExpression(r, align_expr, .none);
1796 try renderToken(r, align_rparen, .space); // )
1797 }
1798
1799 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1800 const align_lparen = tree.firstToken(addrspace_expr) - 1;
1801 const align_rparen = tree.lastToken(addrspace_expr) + 1;
1802
1803 try renderToken(r, align_lparen - 1, .none); // addrspace
1804 try renderToken(r, align_lparen, .none); // (
1805 try renderExpression(r, addrspace_expr, .none);
1806 try renderToken(r, align_rparen, .space); // )
1807 }
1808
1809 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1810 const section_lparen = tree.firstToken(section_expr) - 1;
1811 const section_rparen = tree.lastToken(section_expr) + 1;
1812
1813 try renderToken(r, section_lparen - 1, .none); // section
1814 try renderToken(r, section_lparen, .none); // (
1815 try renderExpression(r, section_expr, .none);
1816 try renderToken(r, section_rparen, .space); // )
1817 }
1818
1819 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1820 // Keep in sync with logic in `renderMember`. Search this file for the marker PROMOTE_CALLCONV_INLINE
1821 const is_callconv_inline = mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)));
1822 const is_declaration = fn_proto.name_token != null;
1823 if (!(is_declaration and is_callconv_inline)) {
1824 const callconv_lparen = tree.firstToken(callconv_expr) - 1;
1825 const callconv_rparen = tree.lastToken(callconv_expr) + 1;
1826
1827 try renderToken(r, callconv_lparen - 1, .none); // callconv
1828 try renderToken(r, callconv_lparen, .none); // (
1829 try renderExpression(r, callconv_expr, .none);
1830 try renderToken(r, callconv_rparen, .space); // )
1831 }
1832 }
1833
1834 if (tree.tokenTag(maybe_bang) == .bang) {
1835 try renderToken(r, maybe_bang, .none); // !
1836 }
1837 return renderExpression(r, return_type, space);
1838}
1839
1840fn renderSwitchCase(
1841 r: *Render,
1842 switch_case: Ast.full.SwitchCase,
1843 space: Space,
1844) Error!void {
1845 const ais = r.ais;
1846 const tree = r.tree;
1847 const trailing_comma = tree.tokenTag(switch_case.ast.arrow_token - 1) == .comma;
1848 const has_comment_before_arrow = blk: {
1849 if (switch_case.ast.values.len == 0) break :blk false;
1850 break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
1851 };
1852
1853 // render inline keyword
1854 if (switch_case.inline_token) |some| {
1855 try renderToken(r, some, .space);
1856 }
1857
1858 // Render everything before the arrow
1859 if (switch_case.ast.values.len == 0) {
1860 try renderToken(r, switch_case.ast.arrow_token - 1, .space); // else keyword
1861 } else if (trailing_comma or has_comment_before_arrow) {
1862 // Render each value on a new line
1863 try ais.pushSpace(.comma);
1864 try renderExpressions(r, switch_case.ast.values, .comma);
1865 ais.popSpace();
1866 } else {
1867 // Render on one line
1868 for (switch_case.ast.values) |value_expr| {
1869 try renderExpression(r, value_expr, .comma_space);
1870 }
1871 }
1872
1873 // Render the arrow and everything after it
1874 const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal)
1875 // Newline gets inserted when rendering the target expr.
1876 Space.none
1877 else
1878 Space.space;
1879 const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space;
1880 try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // =>
1881
1882 if (switch_case.payload_token) |payload_token| {
1883 try renderToken(r, payload_token - 1, .none); // pipe
1884 const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk);
1885 if (tree.tokenTag(payload_token) == .asterisk) {
1886 try renderToken(r, payload_token, .none); // asterisk
1887 }
1888 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1889 if (tree.tokenTag(ident + 1) == .comma) {
1890 try renderToken(r, ident + 1, .space); // ,
1891 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier
1892 try renderToken(r, ident + 3, pre_target_space); // pipe
1893 } else {
1894 try renderToken(r, ident + 1, pre_target_space); // pipe
1895 }
1896 }
1897
1898 try renderExpression(r, switch_case.ast.target_expr, space);
1899}
1900
1901fn renderBlock(
1902 r: *Render,
1903 block_node: Ast.Node.Index,
1904 statements: []const Ast.Node.Index,
1905 space: Space,
1906) Error!void {
1907 const tree = r.tree;
1908 const ais = r.ais;
1909 const lbrace = tree.nodeMainToken(block_node);
1910
1911 if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
1912 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
1913 try renderToken(r, lbrace - 1, .space); // :
1914 }
1915 try ais.pushIndent(.normal);
1916 if (statements.len == 0) {
1917 try renderToken(r, lbrace, .none);
1918 ais.popIndent();
1919 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1920 return;
1921 }
1922 try renderToken(r, lbrace, .newline);
1923 return finishRenderBlock(r, block_node, statements, space);
1924}
1925
1926fn finishRenderBlock(
1927 r: *Render,
1928 block_node: Ast.Node.Index,
1929 statements: []const Ast.Node.Index,
1930 space: Space,
1931) Error!void {
1932 const tree = r.tree;
1933 const ais = r.ais;
1934 for (statements, 0..) |stmt, i| {
1935 if (i != 0) try renderExtraNewline(r, stmt);
1936 if (r.fixups.omit_nodes.contains(stmt)) continue;
1937 try ais.pushSpace(.semicolon);
1938 switch (tree.nodeTag(stmt)) {
1939 .global_var_decl,
1940 .local_var_decl,
1941 .simple_var_decl,
1942 .aligned_var_decl,
1943 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1944
1945 else => try renderExpression(r, stmt, .semicolon),
1946 }
1947 ais.popSpace();
1948 }
1949 ais.popIndent();
1950
1951 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1952}
1953
1954fn renderStructInit(
1955 r: *Render,
1956 struct_node: Ast.Node.Index,
1957 struct_init: Ast.full.StructInit,
1958 space: Space,
1959) Error!void {
1960 const tree = r.tree;
1961 const ais = r.ais;
1962
1963 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
1964 try renderExpression(r, type_expr, .none); // T
1965 } else {
1966 try renderToken(r, struct_init.ast.lbrace - 1, .none); // .
1967 }
1968
1969 if (struct_init.ast.fields.len == 0) {
1970 try ais.pushIndent(.normal);
1971 try renderToken(r, struct_init.ast.lbrace, .none); // lbrace
1972 ais.popIndent();
1973 return renderToken(r, struct_init.ast.lbrace + 1, space); // rbrace
1974 }
1975
1976 const rbrace = tree.lastToken(struct_node);
1977 const trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
1978 if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) {
1979 // Render one field init per line.
1980 try ais.pushIndent(.normal);
1981 try renderToken(r, struct_init.ast.lbrace, .newline);
1982
1983 try renderToken(r, struct_init.ast.lbrace + 1, .none); // .
1984 try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name
1985 // Don't output a space after the = if expression is a multiline string,
1986 // since then it will start on the next line.
1987 const field_node = struct_init.ast.fields[0];
1988 const expr = tree.nodeTag(field_node);
1989 var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
1990 try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
1991
1992 try ais.pushSpace(.comma);
1993 try renderExpressionFixup(r, field_node, .comma);
1994 ais.popSpace();
1995
1996 for (struct_init.ast.fields[1..]) |field_init| {
1997 const init_token = tree.firstToken(field_init);
1998 try renderExtraNewlineToken(r, init_token - 3);
1999 try renderToken(r, init_token - 3, .none); // .
2000 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2001 space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space;
2002 try renderToken(r, init_token - 1, space_after_equal); // =
2003
2004 try ais.pushSpace(.comma);
2005 try renderExpressionFixup(r, field_init, .comma);
2006 ais.popSpace();
2007 }
2008
2009 ais.popIndent();
2010 } else {
2011 // Render all on one line, no trailing comma.
2012 try renderToken(r, struct_init.ast.lbrace, .space);
2013
2014 for (struct_init.ast.fields) |field_init| {
2015 const init_token = tree.firstToken(field_init);
2016 try renderToken(r, init_token - 3, .none); // .
2017 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2018 try renderToken(r, init_token - 1, .space); // =
2019 try renderExpressionFixup(r, field_init, .comma_space);
2020 }
2021 }
2022
2023 return renderToken(r, rbrace, space);
2024}
2025
2026fn renderArrayInit(
2027 r: *Render,
2028 array_init: Ast.full.ArrayInit,
2029 space: Space,
2030) Error!void {
2031 const tree = r.tree;
2032 const ais = r.ais;
2033 const gpa = r.gpa;
2034
2035 if (array_init.ast.type_expr.unwrap()) |type_expr| {
2036 try renderExpression(r, type_expr, .none); // T
2037 } else {
2038 try renderToken(r, array_init.ast.lbrace - 1, .none); // .
2039 }
2040
2041 if (array_init.ast.elements.len == 0) {
2042 try ais.pushIndent(.normal);
2043 try renderToken(r, array_init.ast.lbrace, .none); // lbrace
2044 ais.popIndent();
2045 return renderToken(r, array_init.ast.lbrace + 1, space); // rbrace
2046 }
2047
2048 const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
2049 const last_elem_token = tree.lastToken(last_elem);
2050 const trailing_comma = tree.tokenTag(last_elem_token + 1) == .comma;
2051 const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1;
2052 assert(tree.tokenTag(rbrace) == .r_brace);
2053
2054 if (array_init.ast.elements.len == 1) {
2055 const only_elem = array_init.ast.elements[0];
2056 const first_token = tree.firstToken(only_elem);
2057 if (tree.tokenTag(first_token) != .multiline_string_literal_line and
2058 !anythingBetween(tree, last_elem_token, rbrace))
2059 {
2060 try renderToken(r, array_init.ast.lbrace, .none);
2061 try renderExpression(r, only_elem, .none);
2062 return renderToken(r, rbrace, space);
2063 }
2064 }
2065
2066 const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
2067 const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
2068
2069 if (!trailing_comma and !contains_comment and !contains_multiline_string) {
2070 // Render all on one line, no trailing comma.
2071 if (array_init.ast.elements.len == 1) {
2072 // If there is only one element, we don't use spaces
2073 try renderToken(r, array_init.ast.lbrace, .none);
2074 try renderExpression(r, array_init.ast.elements[0], .none);
2075 } else {
2076 try renderToken(r, array_init.ast.lbrace, .space);
2077 for (array_init.ast.elements) |elem| {
2078 try renderExpression(r, elem, .comma_space);
2079 }
2080 }
2081 return renderToken(r, last_elem_token + 1, space); // rbrace
2082 }
2083
2084 try ais.pushIndent(.normal);
2085 try renderToken(r, array_init.ast.lbrace, .newline);
2086
2087 var expr_index: usize = 0;
2088 while (true) {
2089 const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2090 const row_exprs = array_init.ast.elements[expr_index..];
2091 // A place to store the width of each expression and its column's maximum
2092 const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2093 defer gpa.free(widths);
2094 @memset(widths, 0);
2095
2096 const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2097 defer gpa.free(expr_newlines);
2098 @memset(expr_newlines, false);
2099
2100 const expr_widths = widths[0..row_exprs.len];
2101 const column_widths = widths[row_exprs.len..];
2102
2103 // Find next row with trailing comment (if any) to end the current section.
2104 const section_end = sec_end: {
2105 var this_line_first_expr: usize = 0;
2106 var this_line_size = rowSize(tree, row_exprs, rbrace);
2107 for (row_exprs, 0..) |expr, i| {
2108 // Ignore comment on first line of this section.
2109 if (i == 0) continue;
2110 const expr_last_token = tree.lastToken(expr);
2111 if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2112 continue;
2113 // Track start of line containing comment.
2114 if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2115 this_line_first_expr = i;
2116 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2117 }
2118
2119 const maybe_comma = expr_last_token + 1;
2120 if (tree.tokenTag(maybe_comma) == .comma) {
2121 if (hasSameLineComment(tree, maybe_comma))
2122 break :sec_end i - this_line_size + 1;
2123 }
2124 }
2125 break :sec_end row_exprs.len;
2126 };
2127 expr_index += section_end;
2128
2129 const section_exprs = row_exprs[0..section_end];
2130
2131 var sub_expr_buffer: std.io.Writer.Allocating = .init(gpa);
2132 defer sub_expr_buffer.deinit();
2133
2134 const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2135 defer gpa.free(sub_expr_buffer_starts);
2136
2137 var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.writer, indent_delta);
2138 defer auto_indenting_stream.deinit();
2139 var sub_render: Render = .{
2140 .gpa = r.gpa,
2141 .ais = &auto_indenting_stream,
2142 .tree = r.tree,
2143 .fixups = r.fixups,
2144 };
2145
2146 // Calculate size of columns in current section
2147 var column_counter: usize = 0;
2148 var single_line = true;
2149 var contains_newline = false;
2150 for (section_exprs, 0..) |expr, i| {
2151 const start = sub_expr_buffer.getWritten().len;
2152 sub_expr_buffer_starts[i] = start;
2153
2154 if (i + 1 < section_exprs.len) {
2155 try renderExpression(&sub_render, expr, .none);
2156 const written = sub_expr_buffer.getWritten();
2157 const width = written.len - start;
2158 const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null;
2159 contains_newline = contains_newline or this_contains_newline;
2160 expr_widths[i] = width;
2161 expr_newlines[i] = this_contains_newline;
2162
2163 if (!this_contains_newline) {
2164 const column = column_counter % row_size;
2165 column_widths[column] = @max(column_widths[column], width);
2166
2167 const expr_last_token = tree.lastToken(expr) + 1;
2168 const next_expr = section_exprs[i + 1];
2169 column_counter += 1;
2170 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2171 } else {
2172 single_line = false;
2173 column_counter = 0;
2174 }
2175 } else {
2176 try ais.pushSpace(.comma);
2177 try renderExpression(&sub_render, expr, .comma);
2178 ais.popSpace();
2179
2180 const written = sub_expr_buffer.getWritten();
2181 const width = written.len - start - 2;
2182 const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null;
2183 contains_newline = contains_newline or this_contains_newline;
2184 expr_widths[i] = width;
2185 expr_newlines[i] = contains_newline;
2186
2187 if (!contains_newline) {
2188 const column = column_counter % row_size;
2189 column_widths[column] = @max(column_widths[column], width);
2190 }
2191 }
2192 }
2193 sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.getWritten().len;
2194
2195 // Render exprs in current section.
2196 column_counter = 0;
2197 for (section_exprs, 0..) |expr, i| {
2198 const start = sub_expr_buffer_starts[i];
2199 const end = sub_expr_buffer_starts[i + 1];
2200 const expr_text = sub_expr_buffer.getWritten()[start..end];
2201 if (!expr_newlines[i]) {
2202 try ais.writeAll(expr_text);
2203 } else {
2204 var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2205 var last_line_was_empty = false;
2206 try ais.writeAll(by_line.first());
2207 while (by_line.next()) |line| {
2208 if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2209 try ais.insertNewline();
2210 } else {
2211 try ais.maybeInsertNewline();
2212 }
2213 last_line_was_empty = (line.len == 0);
2214 try ais.writeAll(line);
2215 }
2216 }
2217
2218 if (i + 1 < section_exprs.len) {
2219 const next_expr = section_exprs[i + 1];
2220 const comma = tree.lastToken(expr) + 1;
2221
2222 if (column_counter != row_size - 1) {
2223 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2224 // Neither the current or next expression is multiline
2225 try renderToken(r, comma, .space); // ,
2226 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2227 const padding = column_widths[column_counter % row_size] - expr_widths[i];
2228 try ais.splatByteAll(' ', padding);
2229
2230 column_counter += 1;
2231 continue;
2232 }
2233 }
2234
2235 if (single_line and row_size != 1) {
2236 try renderToken(r, comma, .space); // ,
2237 continue;
2238 }
2239
2240 column_counter = 0;
2241 try renderToken(r, comma, .newline); // ,
2242 try renderExtraNewline(r, next_expr);
2243 }
2244 }
2245
2246 if (expr_index == array_init.ast.elements.len)
2247 break;
2248 }
2249
2250 ais.popIndent();
2251 return renderToken(r, rbrace, space); // rbrace
2252}
2253
2254fn renderContainerDecl(
2255 r: *Render,
2256 container_decl_node: Ast.Node.Index,
2257 container_decl: Ast.full.ContainerDecl,
2258 space: Space,
2259) Error!void {
2260 const tree = r.tree;
2261 const ais = r.ais;
2262
2263 if (container_decl.layout_token) |layout_token| {
2264 try renderToken(r, layout_token, .space);
2265 }
2266
2267 const container: Container = switch (tree.tokenTag(container_decl.ast.main_token)) {
2268 .keyword_enum => .@"enum",
2269 .keyword_struct => for (container_decl.ast.members) |member| {
2270 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
2271 } else .tuple,
2272 else => .other,
2273 };
2274
2275 var lbrace: Ast.TokenIndex = undefined;
2276 if (container_decl.ast.enum_token) |enum_token| {
2277 try renderToken(r, container_decl.ast.main_token, .none); // union
2278 try renderToken(r, enum_token - 1, .none); // lparen
2279 try renderToken(r, enum_token, .none); // enum
2280 if (container_decl.ast.arg.unwrap()) |arg| {
2281 try renderToken(r, enum_token + 1, .none); // lparen
2282 try renderExpression(r, arg, .none);
2283 const rparen = tree.lastToken(arg) + 1;
2284 try renderToken(r, rparen, .none); // rparen
2285 try renderToken(r, rparen + 1, .space); // rparen
2286 lbrace = rparen + 2;
2287 } else {
2288 try renderToken(r, enum_token + 1, .space); // rparen
2289 lbrace = enum_token + 2;
2290 }
2291 } else if (container_decl.ast.arg.unwrap()) |arg| {
2292 try renderToken(r, container_decl.ast.main_token, .none); // union
2293 try renderToken(r, container_decl.ast.main_token + 1, .none); // lparen
2294 try renderExpression(r, arg, .none);
2295 const rparen = tree.lastToken(arg) + 1;
2296 try renderToken(r, rparen, .space); // rparen
2297 lbrace = rparen + 1;
2298 } else {
2299 try renderToken(r, container_decl.ast.main_token, .space); // union
2300 lbrace = container_decl.ast.main_token + 1;
2301 }
2302
2303 const rbrace = tree.lastToken(container_decl_node);
2304
2305 if (container_decl.ast.members.len == 0) {
2306 try ais.pushIndent(.normal);
2307 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2308 try renderToken(r, lbrace, .newline); // lbrace
2309 try renderContainerDocComments(r, lbrace + 1);
2310 } else {
2311 try renderToken(r, lbrace, .none); // lbrace
2312 }
2313 ais.popIndent();
2314 return renderToken(r, rbrace, space); // rbrace
2315 }
2316
2317 const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
2318 if (!src_has_trailing_comma) one_line: {
2319 // We print all the members in-line unless one of the following conditions are true:
2320
2321 // 1. The container has comments or multiline strings.
2322 if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
2323 break :one_line;
2324 }
2325
2326 // 2. The container has a container comment.
2327 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line;
2328
2329 // 3. A member of the container has a doc comment.
2330 for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| {
2331 if (tag == .doc_comment) break :one_line;
2332 }
2333
2334 // 4. The container has non-field members.
2335 for (container_decl.ast.members) |member| {
2336 if (tree.fullContainerField(member) == null) break :one_line;
2337 }
2338
2339 // Print all the declarations on the same line.
2340 try renderToken(r, lbrace, .space); // lbrace
2341 for (container_decl.ast.members) |member| {
2342 try renderMember(r, container, member, .space);
2343 }
2344 return renderToken(r, rbrace, space); // rbrace
2345 }
2346
2347 // One member per line.
2348 try ais.pushIndent(.normal);
2349 try renderToken(r, lbrace, .newline); // lbrace
2350 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2351 try renderContainerDocComments(r, lbrace + 1);
2352 }
2353 for (container_decl.ast.members, 0..) |member, i| {
2354 if (i != 0) try renderExtraNewline(r, member);
2355 switch (tree.nodeTag(member)) {
2356 // For container fields, ensure a trailing comma is added if necessary.
2357 .container_field_init,
2358 .container_field_align,
2359 .container_field,
2360 => {
2361 try ais.pushSpace(.comma);
2362 try renderMember(r, container, member, .comma);
2363 ais.popSpace();
2364 },
2365
2366 else => try renderMember(r, container, member, .newline),
2367 }
2368 }
2369 ais.popIndent();
2370
2371 return renderToken(r, rbrace, space); // rbrace
2372}
2373
2374fn renderAsmLegacy(
2375 r: *Render,
2376 asm_node: Ast.full.AsmLegacy,
2377 space: Space,
2378) Error!void {
2379 const tree = r.tree;
2380 const ais = r.ais;
2381
2382 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2383
2384 if (asm_node.volatile_token) |volatile_token| {
2385 try renderToken(r, volatile_token, .space); // volatile
2386 try renderToken(r, volatile_token + 1, .none); // lparen
2387 } else {
2388 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2389 }
2390
2391 if (asm_node.ast.items.len == 0) {
2392 try ais.forcePushIndent(.normal);
2393 if (asm_node.first_clobber) |first_clobber| {
2394 // asm ("foo" ::: "a", "b")
2395 // asm ("foo" ::: "a", "b",)
2396 try renderExpression(r, asm_node.ast.template, .space);
2397 // Render the three colons.
2398 try renderToken(r, first_clobber - 3, .none);
2399 try renderToken(r, first_clobber - 2, .none);
2400 try renderToken(r, first_clobber - 1, .space);
2401
2402 try ais.writeAll(".{ ");
2403
2404 var tok_i = first_clobber;
2405 while (true) : (tok_i += 1) {
2406 try ais.writeByte('.');
2407 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2408 try ais.writeAll(" = true");
2409
2410 tok_i += 1;
2411 switch (tree.tokenTag(tok_i)) {
2412 .r_paren => {
2413 try ais.writeAll(" }");
2414 ais.popIndent();
2415 return renderToken(r, tok_i, space);
2416 },
2417 .comma => {
2418 if (tree.tokenTag(tok_i + 1) == .r_paren) {
2419 try ais.writeAll(" }");
2420 ais.popIndent();
2421 return renderToken(r, tok_i + 1, space);
2422 } else {
2423 try renderToken(r, tok_i, .space);
2424 }
2425 },
2426 else => unreachable,
2427 }
2428 }
2429 } else {
2430 unreachable;
2431 }
2432 }
2433
2434 try ais.forcePushIndent(.normal);
2435 try renderExpression(r, asm_node.ast.template, .newline);
2436 ais.setIndentDelta(asm_indent_delta);
2437 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2438
2439 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2440 try renderToken(r, colon1, .newline); // :
2441 break :colon2 colon1 + 1;
2442 } else colon2: {
2443 try renderToken(r, colon1, .space); // :
2444
2445 try ais.forcePushIndent(.normal);
2446 for (asm_node.outputs, 0..) |asm_output, i| {
2447 if (i + 1 < asm_node.outputs.len) {
2448 const next_asm_output = asm_node.outputs[i + 1];
2449 try renderAsmOutput(r, asm_output, .none);
2450
2451 const comma = tree.firstToken(next_asm_output) - 1;
2452 try renderToken(r, comma, .newline); // ,
2453 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2454 } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
2455 try ais.pushSpace(.comma);
2456 try renderAsmOutput(r, asm_output, .comma);
2457 ais.popSpace();
2458 ais.popIndent();
2459 ais.setIndentDelta(indent_delta);
2460 ais.popIndent();
2461 return renderToken(r, asm_node.ast.rparen, space); // rparen
2462 } else {
2463 try ais.pushSpace(.comma);
2464 try renderAsmOutput(r, asm_output, .comma);
2465 ais.popSpace();
2466 const comma_or_colon = tree.lastToken(asm_output) + 1;
2467 ais.popIndent();
2468 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2469 .comma => comma_or_colon + 1,
2470 else => comma_or_colon,
2471 };
2472 }
2473 } else unreachable;
2474 };
2475
2476 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2477 try renderToken(r, colon2, .newline); // :
2478 break :colon3 colon2 + 1;
2479 } else colon3: {
2480 try renderToken(r, colon2, .space); // :
2481 try ais.forcePushIndent(.normal);
2482 for (asm_node.inputs, 0..) |asm_input, i| {
2483 if (i + 1 < asm_node.inputs.len) {
2484 const next_asm_input = asm_node.inputs[i + 1];
2485 try renderAsmInput(r, asm_input, .none);
2486
2487 const first_token = tree.firstToken(next_asm_input);
2488 try renderToken(r, first_token - 1, .newline); // ,
2489 try renderExtraNewlineToken(r, first_token);
2490 } else if (asm_node.first_clobber == null) {
2491 try ais.pushSpace(.comma);
2492 try renderAsmInput(r, asm_input, .comma);
2493 ais.popSpace();
2494 ais.popIndent();
2495 ais.setIndentDelta(indent_delta);
2496 ais.popIndent();
2497 return renderToken(r, asm_node.ast.rparen, space); // rparen
2498 } else {
2499 try ais.pushSpace(.comma);
2500 try renderAsmInput(r, asm_input, .comma);
2501 ais.popSpace();
2502 const comma_or_colon = tree.lastToken(asm_input) + 1;
2503 ais.popIndent();
2504 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2505 .comma => comma_or_colon + 1,
2506 else => comma_or_colon,
2507 };
2508 }
2509 }
2510 unreachable;
2511 };
2512
2513 try renderToken(r, colon3, .space); // :
2514 try ais.writeAll(".{ ");
2515 const first_clobber = asm_node.first_clobber.?;
2516 var tok_i = first_clobber;
2517 while (true) {
2518 switch (tree.tokenTag(tok_i + 1)) {
2519 .r_paren => {
2520 ais.setIndentDelta(indent_delta);
2521 try ais.writeByte('.');
2522 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2523 try ais.writeAll(" = true }");
2524 try renderSpace(r, tok_i, lexeme_len, .newline);
2525 ais.popIndent();
2526 return renderToken(r, tok_i + 1, space);
2527 },
2528 .comma => {
2529 switch (tree.tokenTag(tok_i + 2)) {
2530 .r_paren => {
2531 ais.setIndentDelta(indent_delta);
2532 try ais.writeByte('.');
2533 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2534 try ais.writeAll(" = true }");
2535 try renderSpace(r, tok_i, lexeme_len, .newline);
2536 ais.popIndent();
2537 return renderToken(r, tok_i + 2, space);
2538 },
2539 else => {
2540 try ais.writeByte('.');
2541 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2542 try ais.writeAll(" = true");
2543 try renderToken(r, tok_i + 1, .space);
2544 tok_i += 2;
2545 },
2546 }
2547 },
2548 else => unreachable,
2549 }
2550 }
2551}
2552
2553fn renderAsm(
2554 r: *Render,
2555 asm_node: Ast.full.Asm,
2556 space: Space,
2557) Error!void {
2558 const tree = r.tree;
2559 const ais = r.ais;
2560
2561 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2562
2563 if (asm_node.volatile_token) |volatile_token| {
2564 try renderToken(r, volatile_token, .space); // volatile
2565 try renderToken(r, volatile_token + 1, .none); // lparen
2566 } else {
2567 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2568 }
2569
2570 if (asm_node.ast.items.len == 0) {
2571 try ais.forcePushIndent(.normal);
2572 if (asm_node.ast.clobbers.unwrap()) |clobbers| {
2573 // asm ("foo" ::: clobbers)
2574 try renderExpression(r, asm_node.ast.template, .space);
2575 // Render the three colons.
2576 const first_clobber = tree.firstToken(clobbers);
2577 try renderToken(r, first_clobber - 3, .none);
2578 try renderToken(r, first_clobber - 2, .none);
2579 try renderToken(r, first_clobber - 1, .space);
2580 try renderExpression(r, clobbers, .none);
2581 ais.popIndent();
2582 return renderToken(r, asm_node.ast.rparen, space); // rparen
2583 }
2584
2585 // asm ("foo")
2586 try renderExpression(r, asm_node.ast.template, .none);
2587 ais.popIndent();
2588 return renderToken(r, asm_node.ast.rparen, space); // rparen
2589 }
2590
2591 try ais.forcePushIndent(.normal);
2592 try renderExpression(r, asm_node.ast.template, .newline);
2593 ais.setIndentDelta(asm_indent_delta);
2594 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2595
2596 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2597 try renderToken(r, colon1, .newline); // :
2598 break :colon2 colon1 + 1;
2599 } else colon2: {
2600 try renderToken(r, colon1, .space); // :
2601
2602 try ais.forcePushIndent(.normal);
2603 for (asm_node.outputs, 0..) |asm_output, i| {
2604 if (i + 1 < asm_node.outputs.len) {
2605 const next_asm_output = asm_node.outputs[i + 1];
2606 try renderAsmOutput(r, asm_output, .none);
2607
2608 const comma = tree.firstToken(next_asm_output) - 1;
2609 try renderToken(r, comma, .newline); // ,
2610 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2611 } else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
2612 try ais.pushSpace(.comma);
2613 try renderAsmOutput(r, asm_output, .comma);
2614 ais.popSpace();
2615 ais.popIndent();
2616 ais.setIndentDelta(indent_delta);
2617 ais.popIndent();
2618 return renderToken(r, asm_node.ast.rparen, space); // rparen
2619 } else {
2620 try ais.pushSpace(.comma);
2621 try renderAsmOutput(r, asm_output, .comma);
2622 ais.popSpace();
2623 const comma_or_colon = tree.lastToken(asm_output) + 1;
2624 ais.popIndent();
2625 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2626 .comma => comma_or_colon + 1,
2627 else => comma_or_colon,
2628 };
2629 }
2630 } else unreachable;
2631 };
2632
2633 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2634 try renderToken(r, colon2, .newline); // :
2635 break :colon3 colon2 + 1;
2636 } else colon3: {
2637 try renderToken(r, colon2, .space); // :
2638 try ais.forcePushIndent(.normal);
2639 for (asm_node.inputs, 0..) |asm_input, i| {
2640 if (i + 1 < asm_node.inputs.len) {
2641 const next_asm_input = asm_node.inputs[i + 1];
2642 try renderAsmInput(r, asm_input, .none);
2643
2644 const first_token = tree.firstToken(next_asm_input);
2645 try renderToken(r, first_token - 1, .newline); // ,
2646 try renderExtraNewlineToken(r, first_token);
2647 } else if (asm_node.ast.clobbers == .none) {
2648 try ais.pushSpace(.comma);
2649 try renderAsmInput(r, asm_input, .comma);
2650 ais.popSpace();
2651 ais.popIndent();
2652 ais.setIndentDelta(indent_delta);
2653 ais.popIndent();
2654 return renderToken(r, asm_node.ast.rparen, space); // rparen
2655 } else {
2656 try ais.pushSpace(.comma);
2657 try renderAsmInput(r, asm_input, .comma);
2658 ais.popSpace();
2659 const comma_or_colon = tree.lastToken(asm_input) + 1;
2660 ais.popIndent();
2661 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2662 .comma => comma_or_colon + 1,
2663 else => comma_or_colon,
2664 };
2665 }
2666 }
2667 unreachable;
2668 };
2669
2670 try renderToken(r, colon3, .space); // :
2671 const clobbers = asm_node.ast.clobbers.unwrap().?;
2672 try renderExpression(r, clobbers, .none);
2673 ais.setIndentDelta(indent_delta);
2674 ais.popIndent();
2675 return renderToken(r, asm_node.ast.rparen, space); // rparen
2676}
2677
2678fn renderCall(
2679 r: *Render,
2680 call: Ast.full.Call,
2681 space: Space,
2682) Error!void {
2683 try renderExpression(r, call.ast.fn_expr, .none);
2684 try renderParamList(r, call.ast.lparen, call.ast.params, space);
2685}
2686
2687fn renderParamList(
2688 r: *Render,
2689 lparen: Ast.TokenIndex,
2690 params: []const Ast.Node.Index,
2691 space: Space,
2692) Error!void {
2693 const tree = r.tree;
2694 const ais = r.ais;
2695
2696 if (params.len == 0) {
2697 try ais.pushIndent(.normal);
2698 try renderToken(r, lparen, .none);
2699 ais.popIndent();
2700 return renderToken(r, lparen + 1, space); // )
2701 }
2702
2703 const last_param = params[params.len - 1];
2704 const after_last_param_tok = tree.lastToken(last_param) + 1;
2705 if (tree.tokenTag(after_last_param_tok) == .comma) {
2706 try ais.pushIndent(.normal);
2707 try renderToken(r, lparen, .newline); // (
2708 for (params, 0..) |param_node, i| {
2709 if (i + 1 < params.len) {
2710 try renderExpression(r, param_node, .none);
2711
2712 const comma = tree.lastToken(param_node) + 1;
2713 try renderToken(r, comma, .newline); // ,
2714
2715 try renderExtraNewline(r, params[i + 1]);
2716 } else {
2717 try ais.pushSpace(.comma);
2718 try renderExpression(r, param_node, .comma);
2719 ais.popSpace();
2720 }
2721 }
2722 ais.popIndent();
2723 return renderToken(r, after_last_param_tok + 1, space); // )
2724 }
2725
2726 try ais.pushIndent(.normal);
2727 try renderToken(r, lparen, .none); // (
2728 for (params, 0..) |param_node, i| {
2729 try renderExpression(r, param_node, .none);
2730
2731 if (i + 1 < params.len) {
2732 const comma = tree.lastToken(param_node) + 1;
2733 const next_multiline_string =
2734 tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line;
2735 const comma_space: Space = if (next_multiline_string) .none else .space;
2736 try renderToken(r, comma, comma_space);
2737 }
2738 }
2739 ais.popIndent();
2740 return renderToken(r, after_last_param_tok, space); // )
2741}
2742
2743/// Render an expression, and the comma that follows it, if it is present in the source.
2744/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2745fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
2746 const tree = r.tree;
2747 const maybe_comma = tree.lastToken(node) + 1;
2748 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2749 try renderExpression(r, node, .none);
2750 return renderToken(r, maybe_comma, space);
2751 } else {
2752 return renderExpression(r, node, space);
2753 }
2754}
2755
2756/// Render a token, and the comma that follows it, if it is present in the source.
2757/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2758fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
2759 const tree = r.tree;
2760 const maybe_comma = token + 1;
2761 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2762 try renderToken(r, token, .none);
2763 return renderToken(r, maybe_comma, space);
2764 } else {
2765 return renderToken(r, token, space);
2766 }
2767}
2768
2769/// Render an identifier, and the comma that follows it, if it is present in the source.
2770/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2771fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2772 const tree = r.tree;
2773 const maybe_comma = token + 1;
2774 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2775 try renderIdentifier(r, token, .none, quote);
2776 return renderToken(r, maybe_comma, space);
2777 } else {
2778 return renderIdentifier(r, token, space, quote);
2779 }
2780}
2781
2782const Space = enum {
2783 /// Output the token lexeme only.
2784 none,
2785 /// Output the token lexeme followed by a single space.
2786 space,
2787 /// Output the token lexeme followed by a newline.
2788 newline,
2789 /// If the next token is a comma, render it as well. If not, insert one.
2790 /// In either case, a newline will be inserted afterwards.
2791 comma,
2792 /// Additionally consume the next token if it is a comma.
2793 /// In either case, a space will be inserted afterwards.
2794 comma_space,
2795 /// Additionally consume the next token if it is a semicolon.
2796 /// In either case, a newline will be inserted afterwards.
2797 semicolon,
2798 /// Skip rendering whitespace and comments. If this is used, the caller
2799 /// *must* handle whitespace and comments manually.
2800 skip,
2801};
2802
2803fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2804 const tree = r.tree;
2805 const ais = r.ais;
2806 const lexeme = tokenSliceForRender(tree, token_index);
2807 try ais.writeAll(lexeme);
2808 try renderSpace(r, token_index, lexeme.len, space);
2809}
2810
2811fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: Space, override_space: Space) Error!void {
2812 const tree = r.tree;
2813 const ais = r.ais;
2814 const lexeme = tokenSliceForRender(tree, token_index);
2815 try ais.writeAll(lexeme);
2816 ais.enableSpaceMode(override_space);
2817 defer ais.disableSpaceMode();
2818 try renderSpace(r, token_index, lexeme.len, space);
2819}
2820
2821fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2822 const tree = r.tree;
2823 const ais = r.ais;
2824
2825 const next_token_tag = tree.tokenTag(token_index + 1);
2826
2827 if (space == .skip) return;
2828
2829 if (space == .comma and next_token_tag != .comma) {
2830 try ais.writeByte(',');
2831 }
2832 if (space == .semicolon or space == .comma) ais.enableSpaceMode(space);
2833 defer ais.disableSpaceMode();
2834 const comment = try renderComments(
2835 r,
2836 tree.tokenStart(token_index) + lexeme_len,
2837 tree.tokenStart(token_index + 1),
2838 );
2839 switch (space) {
2840 .none => {},
2841 .space => if (!comment) try ais.writeByte(' '),
2842 .newline => if (!comment) try ais.insertNewline(),
2843
2844 .comma => if (next_token_tag == .comma) {
2845 try renderToken(r, token_index + 1, .newline);
2846 } else if (!comment) {
2847 try ais.insertNewline();
2848 },
2849
2850 .comma_space => if (next_token_tag == .comma) {
2851 try renderToken(r, token_index + 1, .space);
2852 } else if (!comment) {
2853 try ais.writeByte(' ');
2854 },
2855
2856 .semicolon => if (next_token_tag == .semicolon) {
2857 try renderToken(r, token_index + 1, .newline);
2858 } else if (!comment) {
2859 try ais.insertNewline();
2860 },
2861
2862 .skip => unreachable,
2863 }
2864}
2865
2866fn renderOnlySpace(r: *Render, space: Space) Error!void {
2867 const ais = r.ais;
2868 switch (space) {
2869 .none => {},
2870 .space => try ais.writeByte(' '),
2871 .newline => try ais.insertNewline(),
2872 .comma => try ais.writeAll(",\n"),
2873 .comma_space => try ais.writeAll(", "),
2874 .semicolon => try ais.writeAll(";\n"),
2875 .skip => unreachable,
2876 }
2877}
2878
2879const QuoteBehavior = enum {
2880 preserve_when_shadowing,
2881 eagerly_unquote,
2882 eagerly_unquote_except_underscore,
2883};
2884
2885fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2886 const tree = r.tree;
2887 assert(tree.tokenTag(token_index) == .identifier);
2888 const lexeme = tokenSliceForRender(tree, token_index);
2889
2890 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2891 try r.ais.writeAll(mangled);
2892 try renderSpace(r, token_index, lexeme.len, space);
2893 return;
2894 }
2895
2896 if (lexeme[0] != '@') {
2897 return renderToken(r, token_index, space);
2898 }
2899
2900 assert(lexeme.len >= 3);
2901 assert(lexeme[0] == '@');
2902 assert(lexeme[1] == '\"');
2903 assert(lexeme[lexeme.len - 1] == '\"');
2904 const contents = lexeme[2 .. lexeme.len - 1]; // inside the @"" quotation
2905
2906 // Empty name can't be unquoted.
2907 if (contents.len == 0) {
2908 return renderQuotedIdentifier(r, token_index, space, false);
2909 }
2910
2911 // Special case for _.
2912 if (std.zig.isUnderscore(contents)) switch (quote) {
2913 .eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
2914 .eagerly_unquote_except_underscore,
2915 .preserve_when_shadowing,
2916 => return renderQuotedIdentifier(r, token_index, space, false),
2917 };
2918
2919 // Scan the entire name for characters that would (after un-escaping) be illegal in a symbol,
2920 // i.e. contents don't match: [A-Za-z_][A-Za-z0-9_]*
2921 var contents_i: usize = 0;
2922 while (contents_i < contents.len) {
2923 switch (contents[contents_i]) {
2924 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2925 'A'...'Z', 'a'...'z', '_' => {},
2926 '\\' => {
2927 var esc_offset = contents_i;
2928 const res = std.zig.string_literal.parseEscapeSequence(contents, &esc_offset);
2929 switch (res) {
2930 .success => |char| switch (char) {
2931 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2932 'A'...'Z', 'a'...'z', '_' => {},
2933 else => return renderQuotedIdentifier(r, token_index, space, false),
2934 },
2935 .failure => return renderQuotedIdentifier(r, token_index, space, false),
2936 }
2937 contents_i += esc_offset;
2938 continue;
2939 },
2940 else => return renderQuotedIdentifier(r, token_index, space, false),
2941 }
2942 contents_i += 1;
2943 }
2944
2945 // Read enough of the name (while un-escaping) to determine if it's a keyword or primitive.
2946 // If it's too long to fit in this buffer, we know it's neither and quoting is unnecessary.
2947 // If we read the whole thing, we have to do further checks.
2948 const longest_keyword_or_primitive_len = comptime blk: {
2949 var longest = 0;
2950 for (primitives.names.keys()) |key| {
2951 if (key.len > longest) longest = key.len;
2952 }
2953 for (std.zig.Token.keywords.keys()) |key| {
2954 if (key.len > longest) longest = key.len;
2955 }
2956 break :blk longest;
2957 };
2958 var buf: [longest_keyword_or_primitive_len]u8 = undefined;
2959
2960 contents_i = 0;
2961 var buf_i: usize = 0;
2962 while (contents_i < contents.len and buf_i < longest_keyword_or_primitive_len) {
2963 if (contents[contents_i] == '\\') {
2964 const res = std.zig.string_literal.parseEscapeSequence(contents, &contents_i).success;
2965 buf[buf_i] = @as(u8, @intCast(res));
2966 buf_i += 1;
2967 } else {
2968 buf[buf_i] = contents[contents_i];
2969 contents_i += 1;
2970 buf_i += 1;
2971 }
2972 }
2973
2974 // We read the whole thing, so it could be a keyword or primitive.
2975 if (contents_i == contents.len) {
2976 if (!std.zig.isValidId(buf[0..buf_i])) {
2977 return renderQuotedIdentifier(r, token_index, space, false);
2978 }
2979 if (primitives.isPrimitive(buf[0..buf_i])) switch (quote) {
2980 .eagerly_unquote,
2981 .eagerly_unquote_except_underscore,
2982 => return renderQuotedIdentifier(r, token_index, space, true),
2983 .preserve_when_shadowing => return renderQuotedIdentifier(r, token_index, space, false),
2984 };
2985 }
2986
2987 try renderQuotedIdentifier(r, token_index, space, true);
2988}
2989
2990// Renders a @"" quoted identifier, normalizing escapes.
2991// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
2992// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2993fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2994 const tree = r.tree;
2995 const ais = r.ais;
2996 assert(tree.tokenTag(token_index) == .identifier);
2997 const lexeme = tokenSliceForRender(tree, token_index);
2998 assert(lexeme.len >= 3 and lexeme[0] == '@');
2999
3000 if (!unquote) try ais.writeAll("@\"");
3001 const contents = lexeme[2 .. lexeme.len - 1];
3002 try renderIdentifierContents(ais, contents);
3003 if (!unquote) try ais.writeByte('\"');
3004
3005 try renderSpace(r, token_index, lexeme.len, space);
3006}
3007
3008fn renderIdentifierContents(ais: *AutoIndentingStream, bytes: []const u8) !void {
3009 var pos: usize = 0;
3010 while (pos < bytes.len) {
3011 const byte = bytes[pos];
3012 switch (byte) {
3013 '\\' => {
3014 const old_pos = pos;
3015 const res = std.zig.string_literal.parseEscapeSequence(bytes, &pos);
3016 const escape_sequence = bytes[old_pos..pos];
3017 switch (res) {
3018 .success => |codepoint| {
3019 if (codepoint <= 0x7f) {
3020 const buf = [1]u8{@as(u8, @intCast(codepoint))};
3021 try ais.print("{f}", .{std.zig.fmtString(&buf)});
3022 } else {
3023 try ais.writeAll(escape_sequence);
3024 }
3025 },
3026 .failure => {
3027 try ais.writeAll(escape_sequence);
3028 },
3029 }
3030 },
3031 0x00...('\\' - 1), ('\\' + 1)...0x7f => {
3032 const buf = [1]u8{byte};
3033 try ais.print("{f}", .{std.zig.fmtString(&buf)});
3034 pos += 1;
3035 },
3036 0x80...0xff => {
3037 try ais.writeByte(byte);
3038 pos += 1;
3039 },
3040 }
3041 }
3042}
3043
3044/// Returns true if there exists a line comment between any of the tokens from
3045/// `start_token` to `end_token`. This is used to determine if e.g. a
3046/// fn_proto should be wrapped and have a trailing comma inserted even if
3047/// there is none in the source.
3048fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3049 for (start_token..end_token) |i| {
3050 const token: Ast.TokenIndex = @intCast(i);
3051 const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
3052 const end = tree.tokenStart(token + 1);
3053 if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
3054 }
3055
3056 return false;
3057}
3058
3059/// Returns true if there exists a multiline string literal between the start
3060/// of token `start_token` and the start of token `end_token`.
3061fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3062 return std.mem.indexOfScalar(
3063 Token.Tag,
3064 tree.tokens.items(.tag)[start_token..end_token],
3065 .multiline_string_literal_line,
3066 ) != null;
3067}
3068
3069/// Assumes that start is the first byte past the previous token and
3070/// that end is the last byte before the next token.
3071fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
3072 const tree = r.tree;
3073 const ais = r.ais;
3074
3075 var index: usize = start;
3076 while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| {
3077 const comment_start = index + offset;
3078
3079 // If there is no newline, the comment ends with EOF
3080 const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n');
3081 const newline = if (newline_index) |i| comment_start + i else null;
3082
3083 const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len];
3084 const trimmed_comment = mem.trimEnd(u8, untrimmed_comment, &std.ascii.whitespace);
3085
3086 // Don't leave any whitespace at the start of the file
3087 if (index != 0) {
3088 if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) {
3089 // Leave up to one empty line before the first comment
3090 try ais.insertNewline();
3091 try ais.insertNewline();
3092 } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) {
3093 // Respect the newline directly before the comment.
3094 // Note: This allows an empty line between comments
3095 try ais.insertNewline();
3096 } else if (index == start) {
3097 // Otherwise if the first comment is on the same line as
3098 // the token before it, prefix it with a single space.
3099 try ais.writeByte(' ');
3100 }
3101 }
3102
3103 index = 1 + (newline orelse end - 1);
3104
3105 const comment_content = mem.trimStart(u8, trimmed_comment["//".len..], &std.ascii.whitespace);
3106 if (ais.disabled_offset != null and mem.eql(u8, comment_content, "zig fmt: on")) {
3107 // Write the source for which formatting was disabled directly
3108 // to the underlying writer, fixing up invalid whitespace.
3109 const disabled_source = tree.source[ais.disabled_offset.?..comment_start];
3110 try writeFixingWhitespace(ais.underlying_writer, disabled_source);
3111 // Write with the canonical single space.
3112 try ais.underlying_writer.writeAll("// zig fmt: on\n");
3113 ais.disabled_offset = null;
3114 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
3115 // Write with the canonical single space.
3116 try ais.writeAll("// zig fmt: off\n");
3117 ais.disabled_offset = index;
3118 } else {
3119 // Write the comment minus trailing whitespace.
3120 try ais.print("{s}\n", .{trimmed_comment});
3121 }
3122 }
3123
3124 if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
3125 // Don't leave any whitespace at the end of the file
3126 if (end != tree.source.len) {
3127 try ais.insertNewline();
3128 }
3129 }
3130
3131 return index != start;
3132}
3133
3134fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
3135 return renderExtraNewlineToken(r, r.tree.firstToken(node));
3136}
3137
3138/// Check if there is an empty line immediately before the given token. If so, render it.
3139fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
3140 const tree = r.tree;
3141 const ais = r.ais;
3142 const token_start = tree.tokenStart(token_index);
3143 if (token_start == 0) return;
3144 const prev_token_end = if (token_index == 0)
3145 0
3146 else
3147 tree.tokenStart(token_index - 1) + tokenSliceForRender(tree, token_index - 1).len;
3148
3149 // If there is a immediately preceding comment or doc_comment,
3150 // skip it because required extra newline has already been rendered.
3151 if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return;
3152 if (tree.isTokenPrecededByTags(token_index, &.{.doc_comment})) return;
3153
3154 // Iterate backwards to the end of the previous token, stopping if a
3155 // non-whitespace character is encountered or two newlines have been found.
3156 var i = token_start - 1;
3157 var newlines: u2 = 0;
3158 while (std.ascii.isWhitespace(tree.source[i])) : (i -= 1) {
3159 if (tree.source[i] == '\n') newlines += 1;
3160 if (newlines == 2) return ais.insertNewline();
3161 if (i == prev_token_end) break;
3162 }
3163}
3164
3165/// end_token is the token one past the last doc comment token. This function
3166/// searches backwards from there.
3167fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
3168 const tree = r.tree;
3169 // Search backwards for the first doc comment.
3170 if (end_token == 0) return;
3171 var tok = end_token - 1;
3172 while (tree.tokenTag(tok) == .doc_comment) {
3173 if (tok == 0) break;
3174 tok -= 1;
3175 } else {
3176 tok += 1;
3177 }
3178 const first_tok = tok;
3179 if (first_tok == end_token) return;
3180
3181 if (first_tok != 0) {
3182 const prev_token_tag = tree.tokenTag(first_tok - 1);
3183
3184 // Prevent accidental use of `renderDocComments` for a function argument doc comment
3185 assert(prev_token_tag != .l_paren);
3186
3187 if (prev_token_tag != .l_brace) {
3188 try renderExtraNewlineToken(r, first_tok);
3189 }
3190 }
3191
3192 while (tree.tokenTag(tok) == .doc_comment) : (tok += 1) {
3193 try renderToken(r, tok, .newline);
3194 }
3195}
3196
3197/// start_token is first container doc comment token.
3198fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
3199 const tree = r.tree;
3200 var tok = start_token;
3201 while (tree.tokenTag(tok) == .container_doc_comment) : (tok += 1) {
3202 try renderToken(r, tok, .newline);
3203 }
3204 // Render extra newline if there is one between final container doc comment and
3205 // the next token. If the next token is a doc comment, that code path
3206 // will have its own logic to insert a newline.
3207 if (tree.tokenTag(tok) != .doc_comment) {
3208 try renderExtraNewlineToken(r, tok);
3209 }
3210}
3211
3212fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3213 const tree = &r.tree;
3214 const ais = r.ais;
3215 var buf: [1]Ast.Node.Index = undefined;
3216 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3217 var it = fn_proto.iterate(tree);
3218 while (it.next()) |param| {
3219 const name_ident = param.name_token.?;
3220 assert(tree.tokenTag(name_ident) == .identifier);
3221 try ais.writeAll("_ = ");
3222 try ais.writeAll(tokenSliceForRender(r.tree, name_ident));
3223 try ais.writeAll(";\n");
3224 }
3225}
3226
3227fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
3228 var ret = tree.tokenSlice(token_index);
3229 switch (tree.tokenTag(token_index)) {
3230 .container_doc_comment, .doc_comment => {
3231 ret = mem.trimEnd(u8, ret, &std.ascii.whitespace);
3232 },
3233 else => {},
3234 }
3235 return ret;
3236}
3237
3238fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usize {
3239 const tree = r.tree;
3240 const ais = r.ais;
3241 assert(tree.tokenTag(token_index) == .string_literal);
3242 const lexeme = tokenSliceForRender(tree, token_index);
3243 const unquoted = lexeme[1..][0 .. lexeme.len - 2];
3244 if (std.zig.isValidId(unquoted)) {
3245 try ais.writeAll(unquoted);
3246 return unquoted.len;
3247 } else {
3248 try ais.writeByte('@');
3249 try ais.writeAll(lexeme);
3250 return lexeme.len + 1;
3251 }
3252}
3253
3254fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3255 const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3256 for (between_source) |byte| switch (byte) {
3257 '\n' => return false,
3258 '/' => return true,
3259 else => continue,
3260 };
3261 return false;
3262}
3263
3264/// Returns `true` if and only if there are any tokens or line comments between
3265/// start_token and end_token.
3266fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3267 if (start_token + 1 != end_token) return true;
3268 const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3269 for (between_source) |byte| switch (byte) {
3270 '/' => return true,
3271 else => continue,
3272 };
3273 return false;
3274}
3275
3276fn writeFixingWhitespace(w: *Writer, slice: []const u8) Error!void {
3277 for (slice) |byte| switch (byte) {
3278 '\t' => try w.splatByteAll(' ', indent_delta),
3279 '\r' => {},
3280 else => try w.writeByte(byte),
3281 };
3282}
3283
3284fn nodeIsBlock(tag: Ast.Node.Tag) bool {
3285 return switch (tag) {
3286 .block,
3287 .block_semicolon,
3288 .block_two,
3289 .block_two_semicolon,
3290 => true,
3291 else => false,
3292 };
3293}
3294
3295fn nodeIsIfForWhileSwitch(tag: Ast.Node.Tag) bool {
3296 return switch (tag) {
3297 .@"if",
3298 .if_simple,
3299 .@"for",
3300 .for_simple,
3301 .@"while",
3302 .while_simple,
3303 .while_cont,
3304 .@"switch",
3305 .switch_comma,
3306 => true,
3307 else => false,
3308 };
3309}
3310
3311fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
3312 return switch (tag) {
3313 .@"catch",
3314 .add,
3315 .add_wrap,
3316 .array_cat,
3317 .array_mult,
3318 .assign,
3319 .assign_bit_and,
3320 .assign_bit_or,
3321 .assign_shl,
3322 .assign_shr,
3323 .assign_bit_xor,
3324 .assign_div,
3325 .assign_sub,
3326 .assign_sub_wrap,
3327 .assign_mod,
3328 .assign_add,
3329 .assign_add_wrap,
3330 .assign_mul,
3331 .assign_mul_wrap,
3332 .bang_equal,
3333 .bit_and,
3334 .bit_or,
3335 .shl,
3336 .shr,
3337 .bit_xor,
3338 .bool_and,
3339 .bool_or,
3340 .div,
3341 .equal_equal,
3342 .error_union,
3343 .greater_or_equal,
3344 .greater_than,
3345 .less_or_equal,
3346 .less_than,
3347 .merge_error_sets,
3348 .mod,
3349 .mul,
3350 .mul_wrap,
3351 .sub,
3352 .sub_wrap,
3353 .@"orelse",
3354 => true,
3355
3356 else => false,
3357 };
3358}
3359
3360// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3361fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3362 const first_token = tree.firstToken(exprs[0]);
3363 if (tree.tokensOnSameLine(first_token, rtoken)) {
3364 const maybe_comma = rtoken - 1;
3365 if (tree.tokenTag(maybe_comma) == .comma)
3366 return 1;
3367 return exprs.len; // no newlines
3368 }
3369
3370 var count: usize = 1;
3371 for (exprs, 0..) |expr, i| {
3372 if (i + 1 < exprs.len) {
3373 const expr_last_token = tree.lastToken(expr) + 1;
3374 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3375 count += 1;
3376 } else {
3377 return count;
3378 }
3379 }
3380 unreachable;
3381}
3382
3383/// Automatically inserts indentation of written data by keeping
3384/// track of the current indentation level
3385///
3386/// We introduce a new indentation scope with pushIndent/popIndent whenever
3387/// we potentially want to introduce an indent after the next newline.
3388///
3389/// Indentation should only ever increment by one from one line to the next,
3390/// no matter how many new indentation scopes are introduced. This is done by
3391/// only realizing the indentation from the most recent scope. As an example:
3392///
3393/// while (foo) if (bar)
3394/// f(x);
3395///
3396/// The body of `while` introduces a new indentation scope and the body of
3397/// `if` also introduces a new indentation scope. When the newline is seen,
3398/// only the indentation scope of the `if` is realized, and the `while` is
3399/// not.
3400///
3401/// As comments are rendered during space rendering, we need to keep track
3402/// of the appropriate indentation level for them with pushSpace/popSpace.
3403/// This should be done whenever a scope that ends in a .semicolon or a
3404/// .comma is introduced.
3405const AutoIndentingStream = struct {
3406 underlying_writer: *Writer,
3407
3408 /// Offset into the source at which formatting has been disabled with
3409 /// a `zig fmt: off` comment.
3410 ///
3411 /// If non-null, the AutoIndentingStream will not write any bytes
3412 /// to the underlying writer. It will however continue to track the
3413 /// indentation level.
3414 disabled_offset: ?usize = null,
3415
3416 indent_count: usize = 0,
3417 indent_delta: usize,
3418 indent_stack: std.ArrayList(StackElem),
3419 space_stack: std.ArrayList(SpaceElem),
3420 space_mode: ?usize = null,
3421 disable_indent_committing: usize = 0,
3422 current_line_empty: bool = true,
3423 /// the most recently applied indent
3424 applied_indent: usize = 0,
3425
3426 pub const IndentType = enum {
3427 normal,
3428 after_equals,
3429 binop,
3430 field_access,
3431 };
3432 const StackElem = struct {
3433 indent_type: IndentType,
3434 realized: bool,
3435 };
3436 const SpaceElem = struct {
3437 space: Space,
3438 indent_count: usize,
3439 };
3440
3441 pub fn init(gpa: Allocator, w: *Writer, starting_indent_delta: usize) AutoIndentingStream {
3442 return .{
3443 .underlying_writer = w,
3444 .indent_delta = starting_indent_delta,
3445 .indent_stack = .init(gpa),
3446 .space_stack = .init(gpa),
3447 };
3448 }
3449
3450 pub fn deinit(self: *AutoIndentingStream) void {
3451 self.indent_stack.deinit();
3452 self.space_stack.deinit();
3453 }
3454
3455 pub fn writeAll(ais: *AutoIndentingStream, bytes: []const u8) Error!void {
3456 if (bytes.len == 0) return;
3457 try ais.applyIndent();
3458 if (ais.disabled_offset == null) try ais.underlying_writer.writeAll(bytes);
3459 if (bytes[bytes.len - 1] == '\n') ais.resetLine();
3460 }
3461
3462 /// Assumes that if the printed data ends with a newline, it is directly
3463 /// contained in the format string.
3464 pub fn print(ais: *AutoIndentingStream, comptime format: []const u8, args: anytype) Error!void {
3465 try ais.applyIndent();
3466 if (ais.disabled_offset == null) try ais.underlying_writer.print(format, args);
3467 if (format[format.len - 1] == '\n') ais.resetLine();
3468 }
3469
3470 pub fn writeByte(ais: *AutoIndentingStream, byte: u8) Error!void {
3471 try ais.applyIndent();
3472 if (ais.disabled_offset == null) try ais.underlying_writer.writeByte(byte);
3473 assert(byte != '\n');
3474 }
3475
3476 pub fn splatByteAll(ais: *AutoIndentingStream, byte: u8, n: usize) Error!void {
3477 assert(byte != '\n');
3478 try ais.applyIndent();
3479 if (ais.disabled_offset == null) try ais.underlying_writer.splatByteAll(byte, n);
3480 }
3481
3482 // Change the indent delta without changing the final indentation level
3483 pub fn setIndentDelta(ais: *AutoIndentingStream, new_indent_delta: usize) void {
3484 if (ais.indent_delta == new_indent_delta) {
3485 return;
3486 } else if (ais.indent_delta > new_indent_delta) {
3487 assert(ais.indent_delta % new_indent_delta == 0);
3488 ais.indent_count = ais.indent_count * (ais.indent_delta / new_indent_delta);
3489 } else {
3490 // assert that the current indentation (in spaces) in a multiple of the new delta
3491 assert((ais.indent_count * ais.indent_delta) % new_indent_delta == 0);
3492 ais.indent_count = ais.indent_count / (new_indent_delta / ais.indent_delta);
3493 }
3494 ais.indent_delta = new_indent_delta;
3495 }
3496
3497 pub fn insertNewline(ais: *AutoIndentingStream) Error!void {
3498 if (ais.disabled_offset == null) try ais.underlying_writer.writeByte('\n');
3499 ais.resetLine();
3500 }
3501
3502 /// Insert a newline unless the current line is blank
3503 pub fn maybeInsertNewline(ais: *AutoIndentingStream) Error!void {
3504 if (!ais.current_line_empty)
3505 try ais.insertNewline();
3506 }
3507
3508 /// Push an indent that is automatically popped after being applied
3509 pub fn pushIndentOneShot(ais: *AutoIndentingStream) void {
3510 ais.indent_one_shot_count += 1;
3511 ais.pushIndent();
3512 }
3513
3514 /// Turns all one-shot indents into regular indents
3515 /// Returns number of indents that must now be manually popped
3516 pub fn lockOneShotIndent(ais: *AutoIndentingStream) usize {
3517 const locked_count = ais.indent_one_shot_count;
3518 ais.indent_one_shot_count = 0;
3519 return locked_count;
3520 }
3521
3522 /// Push an indent that should not take effect until the next line
3523 pub fn pushIndentNextLine(ais: *AutoIndentingStream) void {
3524 ais.indent_next_line += 1;
3525 ais.pushIndent();
3526 }
3527
3528 /// Checks to see if the most recent indentation exceeds the currently pushed indents
3529 pub fn isLineOverIndented(ais: *AutoIndentingStream) bool {
3530 if (ais.current_line_empty) return false;
3531 return ais.applied_indent > ais.currentIndent();
3532 }
3533
3534 fn resetLine(ais: *AutoIndentingStream) void {
3535 ais.current_line_empty = true;
3536
3537 if (ais.disable_indent_committing > 0) return;
3538
3539 if (ais.indent_stack.items.len > 0) {
3540 // By default, we realize the most recent indentation scope.
3541 var to_realize = ais.indent_stack.items.len - 1;
3542
3543 if (ais.indent_stack.items.len >= 2 and
3544 ais.indent_stack.items[to_realize - 1].indent_type == .after_equals and
3545 ais.indent_stack.items[to_realize - 1].realized and
3546 ais.indent_stack.items[to_realize].indent_type == .binop)
3547 {
3548 // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
3549 // This ensures correct indentation in the below example:
3550 //
3551 // const foo =
3552 // (x >= 'a' and x <= 'z') or //<-- we are here
3553 // (x >= 'A' and x <= 'Z');
3554 //
3555 return;
3556 }
3557
3558 if (ais.indent_stack.items[to_realize].indent_type == .field_access) {
3559 // Only realize the top-most field_access in a chain.
3560 while (to_realize > 0 and ais.indent_stack.items[to_realize - 1].indent_type == .field_access)
3561 to_realize -= 1;
3562 }
3563
3564 if (ais.indent_stack.items[to_realize].realized) return;
3565 ais.indent_stack.items[to_realize].realized = true;
3566 ais.indent_count += 1;
3567 }
3568 }
3569
3570 /// Disables indentation level changes during the next newlines until re-enabled.
3571 pub fn disableIndentCommitting(ais: *AutoIndentingStream) void {
3572 ais.disable_indent_committing += 1;
3573 }
3574
3575 pub fn enableIndentCommitting(ais: *AutoIndentingStream) void {
3576 assert(ais.disable_indent_committing > 0);
3577 ais.disable_indent_committing -= 1;
3578 }
3579
3580 pub fn pushSpace(ais: *AutoIndentingStream, space: Space) !void {
3581 try ais.space_stack.append(.{ .space = space, .indent_count = ais.indent_count });
3582 }
3583
3584 pub fn popSpace(ais: *AutoIndentingStream) void {
3585 _ = ais.space_stack.pop();
3586 }
3587
3588 /// Sets current indentation level to be the same as that of the last pushSpace.
3589 pub fn enableSpaceMode(ais: *AutoIndentingStream, space: Space) void {
3590 if (ais.space_stack.items.len == 0) return;
3591 const curr = ais.space_stack.getLast();
3592 if (curr.space != space) return;
3593 ais.space_mode = curr.indent_count;
3594 }
3595
3596 pub fn disableSpaceMode(ais: *AutoIndentingStream) void {
3597 ais.space_mode = null;
3598 }
3599
3600 pub fn lastSpaceModeIndent(ais: *AutoIndentingStream) usize {
3601 if (ais.space_stack.items.len == 0) return 0;
3602 return ais.space_stack.getLast().indent_count * ais.indent_delta;
3603 }
3604
3605 /// Push default indentation
3606 /// Doesn't actually write any indentation.
3607 /// Just primes the stream to be able to write the correct indentation if it needs to.
3608 pub fn pushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
3609 try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
3610 }
3611
3612 /// Forces an indentation level to be realized.
3613 pub fn forcePushIndent(ais: *AutoIndentingStream, indent_type: IndentType) !void {
3614 try ais.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
3615 ais.indent_count += 1;
3616 }
3617
3618 pub fn popIndent(ais: *AutoIndentingStream) void {
3619 if (ais.indent_stack.pop().?.realized) {
3620 assert(ais.indent_count > 0);
3621 ais.indent_count -= 1;
3622 }
3623 }
3624
3625 pub fn indentStackEmpty(ais: *AutoIndentingStream) bool {
3626 return ais.indent_stack.items.len == 0;
3627 }
3628
3629 /// Writes ' ' bytes if the current line is empty
3630 fn applyIndent(ais: *AutoIndentingStream) Error!void {
3631 const current_indent = ais.currentIndent();
3632 if (ais.current_line_empty and current_indent > 0) {
3633 if (ais.disabled_offset == null) {
3634 try ais.underlying_writer.splatByteAll(' ', current_indent);
3635 }
3636 ais.applied_indent = current_indent;
3637 }
3638 ais.current_line_empty = false;
3639 }
3640
3641 fn currentIndent(ais: *AutoIndentingStream) usize {
3642 const indent_count = ais.space_mode orelse ais.indent_count;
3643 return indent_count * ais.indent_delta;
3644 }
3645};
lib/std/zig/AstGen.zig+28-22
...@@ -11278,10 +11278,14 @@ fn parseStrLit(...@@ -11278,10 +11278,14 @@ fn parseStrLit(
11278 offset: u32,11278 offset: u32,
11279) InnerError!void {11279) InnerError!void {
11280 const raw_string = bytes[offset..];11280 const raw_string = bytes[offset..];
11281 var buf_managed = buf.toManaged(astgen.gpa);11281 const result = r: {
11282 const result = std.zig.string_literal.parseWrite(buf_managed.writer(), raw_string);11282 var aw: std.io.Writer.Allocating = .fromArrayList(astgen.gpa, buf);
11283 buf.* = buf_managed.moveToUnmanaged();11283 defer buf.* = aw.toArrayList();
11284 switch (try result) {11284 break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) {
11285 error.WriteFailed => return error.OutOfMemory,
11286 };
11287 };
11288 switch (result) {
11285 .success => return,11289 .success => return,
11286 .failure => |err| return astgen.failWithStrLitError(err, token, bytes, offset),11290 .failure => |err| return astgen.failWithStrLitError(err, token, bytes, offset),
11287 }11291 }
...@@ -11324,17 +11328,18 @@ fn appendErrorNodeNotes(...@@ -11324,17 +11328,18 @@ fn appendErrorNodeNotes(
11324 notes: []const u32,11328 notes: []const u32,
11325) Allocator.Error!void {11329) Allocator.Error!void {
11326 @branchHint(.cold);11330 @branchHint(.cold);
11331 const gpa = astgen.gpa;
11327 const string_bytes = &astgen.string_bytes;11332 const string_bytes = &astgen.string_bytes;
11328 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);11333 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
11329 try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);11334 try string_bytes.print(gpa, format ++ "\x00", args);
11330 const notes_index: u32 = if (notes.len != 0) blk: {11335 const notes_index: u32 = if (notes.len != 0) blk: {
11331 const notes_start = astgen.extra.items.len;11336 const notes_start = astgen.extra.items.len;
11332 try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len);11337 try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len);
11333 astgen.extra.appendAssumeCapacity(@intCast(notes.len));11338 astgen.extra.appendAssumeCapacity(@intCast(notes.len));
11334 astgen.extra.appendSliceAssumeCapacity(notes);11339 astgen.extra.appendSliceAssumeCapacity(notes);
11335 break :blk @intCast(notes_start);11340 break :blk @intCast(notes_start);
11336 } else 0;11341 } else 0;
11337 try astgen.compile_errors.append(astgen.gpa, .{11342 try astgen.compile_errors.append(gpa, .{
11338 .msg = msg,11343 .msg = msg,
11339 .node = node.toOptional(),11344 .node = node.toOptional(),
11340 .token = .none,11345 .token = .none,
...@@ -11418,7 +11423,7 @@ fn appendErrorTokNotesOff(...@@ -11418,7 +11423,7 @@ fn appendErrorTokNotesOff(
11418 const gpa = astgen.gpa;11423 const gpa = astgen.gpa;
11419 const string_bytes = &astgen.string_bytes;11424 const string_bytes = &astgen.string_bytes;
11420 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);11425 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
11421 try string_bytes.writer(gpa).print(format ++ "\x00", args);11426 try string_bytes.print(gpa, format ++ "\x00", args);
11422 const notes_index: u32 = if (notes.len != 0) blk: {11427 const notes_index: u32 = if (notes.len != 0) blk: {
11423 const notes_start = astgen.extra.items.len;11428 const notes_start = astgen.extra.items.len;
11424 try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len);11429 try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len);
...@@ -11454,7 +11459,7 @@ fn errNoteTokOff(...@@ -11454,7 +11459,7 @@ fn errNoteTokOff(
11454 @branchHint(.cold);11459 @branchHint(.cold);
11455 const string_bytes = &astgen.string_bytes;11460 const string_bytes = &astgen.string_bytes;
11456 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);11461 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
11457 try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);11462 try string_bytes.print(astgen.gpa, format ++ "\x00", args);
11458 return astgen.addExtra(Zir.Inst.CompileErrors.Item{11463 return astgen.addExtra(Zir.Inst.CompileErrors.Item{
11459 .msg = msg,11464 .msg = msg,
11460 .node = .none,11465 .node = .none,
...@@ -11473,7 +11478,7 @@ fn errNoteNode(...@@ -11473,7 +11478,7 @@ fn errNoteNode(
11473 @branchHint(.cold);11478 @branchHint(.cold);
11474 const string_bytes = &astgen.string_bytes;11479 const string_bytes = &astgen.string_bytes;
11475 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);11480 const msg: Zir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
11476 try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);11481 try string_bytes.print(astgen.gpa, format ++ "\x00", args);
11477 return astgen.addExtra(Zir.Inst.CompileErrors.Item{11482 return astgen.addExtra(Zir.Inst.CompileErrors.Item{
11478 .msg = msg,11483 .msg = msg,
11479 .node = node.toOptional(),11484 .node = node.toOptional(),
...@@ -13715,13 +13720,14 @@ fn emitDbgStmtForceCurrentIndex(gz: *GenZir, lc: LineColumn) !void {...@@ -13715,13 +13720,14 @@ fn emitDbgStmtForceCurrentIndex(gz: *GenZir, lc: LineColumn) !void {
13715 } });13720 } });
13716}13721}
1371713722
13718fn lowerAstErrors(astgen: *AstGen) !void {13723fn lowerAstErrors(astgen: *AstGen) error{OutOfMemory}!void {
13719 const gpa = astgen.gpa;13724 const gpa = astgen.gpa;
13720 const tree = astgen.tree;13725 const tree = astgen.tree;
13721 assert(tree.errors.len > 0);13726 assert(tree.errors.len > 0);
1372213727
13723 var msg: std.ArrayListUnmanaged(u8) = .empty;13728 var msg: std.io.Writer.Allocating = .init(gpa);
13724 defer msg.deinit(gpa);13729 defer msg.deinit();
13730 const msg_w = &msg.writer;
1372513731
13726 var notes: std.ArrayListUnmanaged(u32) = .empty;13732 var notes: std.ArrayListUnmanaged(u32) = .empty;
13727 defer notes.deinit(gpa);13733 defer notes.deinit(gpa);
...@@ -13749,26 +13755,26 @@ fn lowerAstErrors(astgen: *AstGen) !void {...@@ -13749,26 +13755,26 @@ fn lowerAstErrors(astgen: *AstGen) !void {
13749 break :blk idx - tok_start;13755 break :blk idx - tok_start;
13750 };13756 };
1375113757
13752 const err: Ast.Error = .{13758 const ast_err: Ast.Error = .{
13753 .tag = Ast.Error.Tag.invalid_byte,13759 .tag = Ast.Error.Tag.invalid_byte,
13754 .token = tok,13760 .token = tok,
13755 .extra = .{ .offset = bad_off },13761 .extra = .{ .offset = bad_off },
13756 };13762 };
13757 msg.clearRetainingCapacity();13763 msg.clearRetainingCapacity();
13758 try tree.renderError(err, msg.writer(gpa));13764 tree.renderError(ast_err, msg_w) catch return error.OutOfMemory;
13759 return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.items}, notes.items);13765 return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.getWritten()}, notes.items);
13760 }13766 }
1376113767
13762 var cur_err = tree.errors[0];13768 var cur_err = tree.errors[0];
13763 for (tree.errors[1..]) |err| {13769 for (tree.errors[1..]) |err| {
13764 if (err.is_note) {13770 if (err.is_note) {
13765 try tree.renderError(err, msg.writer(gpa));13771 tree.renderError(err, msg_w) catch return error.OutOfMemory;
13766 try notes.append(gpa, try astgen.errNoteTok(err.token, "{s}", .{msg.items}));13772 try notes.append(gpa, try astgen.errNoteTok(err.token, "{s}", .{msg.getWritten()}));
13767 } else {13773 } else {
13768 // Flush error13774 // Flush error
13769 const extra_offset = tree.errorOffset(cur_err);13775 const extra_offset = tree.errorOffset(cur_err);
13770 try tree.renderError(cur_err, msg.writer(gpa));13776 tree.renderError(cur_err, msg_w) catch return error.OutOfMemory;
13771 try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items);13777 try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
13772 notes.clearRetainingCapacity();13778 notes.clearRetainingCapacity();
13773 cur_err = err;13779 cur_err = err;
1377413780
...@@ -13781,8 +13787,8 @@ fn lowerAstErrors(astgen: *AstGen) !void {...@@ -13781,8 +13787,8 @@ fn lowerAstErrors(astgen: *AstGen) !void {
1378113787
13782 // Flush error13788 // Flush error
13783 const extra_offset = tree.errorOffset(cur_err);13789 const extra_offset = tree.errorOffset(cur_err);
13784 try tree.renderError(cur_err, msg.writer(gpa));13790 tree.renderError(cur_err, msg_w) catch return error.OutOfMemory;
13785 try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items);13791 try astgen.appendErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
13786}13792}
1378713793
13788const DeclarationName = union(enum) {13794const DeclarationName = union(enum) {
lib/std/zig/ZonGen.zig+69-59
...@@ -1,5 +1,16 @@...@@ -1,5 +1,16 @@
1//! Ingests an `Ast` and produces a `Zoir`.1//! Ingests an `Ast` and produces a `Zoir`.
22
3const std = @import("std");
4const assert = std.debug.assert;
5const mem = std.mem;
6const Allocator = mem.Allocator;
7const StringIndexAdapter = std.hash_map.StringIndexAdapter;
8const StringIndexContext = std.hash_map.StringIndexContext;
9const ZonGen = @This();
10const Zoir = @import("Zoir.zig");
11const Ast = @import("Ast.zig");
12const Writer = std.io.Writer;
13
3gpa: Allocator,14gpa: Allocator,
4tree: Ast,15tree: Ast,
516
...@@ -446,37 +457,44 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator...@@ -446,37 +457,44 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
446 }457 }
447}458}
448459
449fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 {460fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) error{ OutOfMemory, BadString }!u32 {
461 const gpa = zg.gpa;
450 const tree = zg.tree;462 const tree = zg.tree;
451 assert(tree.tokenTag(ident_token) == .identifier);463 assert(tree.tokenTag(ident_token) == .identifier);
452 const ident_name = tree.tokenSlice(ident_token);464 const ident_name = tree.tokenSlice(ident_token);
453 if (!mem.startsWith(u8, ident_name, "@")) {465 if (!mem.startsWith(u8, ident_name, "@")) {
454 const start = zg.string_bytes.items.len;466 const start = zg.string_bytes.items.len;
455 try zg.string_bytes.appendSlice(zg.gpa, ident_name);467 try zg.string_bytes.appendSlice(gpa, ident_name);
456 return @intCast(start);468 return @intCast(start);
457 } else {469 }
458 const offset = 1;470 const offset = 1;
459 const start: u32 = @intCast(zg.string_bytes.items.len);471 const start: u32 = @intCast(zg.string_bytes.items.len);
460 const raw_string = zg.tree.tokenSlice(ident_token)[offset..];472 const raw_string = zg.tree.tokenSlice(ident_token)[offset..];
461 try zg.string_bytes.ensureUnusedCapacity(zg.gpa, raw_string.len);473 try zg.string_bytes.ensureUnusedCapacity(gpa, raw_string.len);
462 switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) {474 const result = r: {
463 .success => {},475 var aw: std.io.Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes);
464 .failure => |err| {476 defer zg.string_bytes = aw.toArrayList();
465 try zg.lowerStrLitError(err, ident_token, raw_string, offset);477 break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) {
466 return error.BadString;478 error.WriteFailed => return error.OutOfMemory,
467 },479 };
468 }480 };
469481 switch (result) {
470 const slice = zg.string_bytes.items[start..];482 .success => {},
471 if (mem.indexOfScalar(u8, slice, 0) != null) {483 .failure => |err| {
472 try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{});484 try zg.lowerStrLitError(err, ident_token, raw_string, offset);
473 return error.BadString;
474 } else if (slice.len == 0) {
475 try zg.addErrorTok(ident_token, "identifier cannot be empty", .{});
476 return error.BadString;485 return error.BadString;
477 }486 },
478 return start;487 }
488
489 const slice = zg.string_bytes.items[start..];
490 if (mem.indexOfScalar(u8, slice, 0) != null) {
491 try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{});
492 return error.BadString;
493 } else if (slice.len == 0) {
494 try zg.addErrorTok(ident_token, "identifier cannot be empty", .{});
495 return error.BadString;
479 }496 }
497 return start;
480}498}
481499
482/// Estimates the size of a string node without parsing it.500/// Estimates the size of a string node without parsing it.
...@@ -507,8 +525,8 @@ pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize {...@@ -507,8 +525,8 @@ pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize {
507pub fn parseStrLit(525pub fn parseStrLit(
508 tree: Ast,526 tree: Ast,
509 node: Ast.Node.Index,527 node: Ast.Node.Index,
510 writer: anytype,528 writer: *Writer,
511) error{OutOfMemory}!std.zig.string_literal.Result {529) Writer.Error!std.zig.string_literal.Result {
512 switch (tree.nodeTag(node)) {530 switch (tree.nodeTag(node)) {
513 .string_literal => {531 .string_literal => {
514 const token = tree.nodeMainToken(node);532 const token = tree.nodeMainToken(node);
...@@ -543,15 +561,22 @@ const StringLiteralResult = union(enum) {...@@ -543,15 +561,22 @@ const StringLiteralResult = union(enum) {
543 slice: struct { start: u32, len: u32 },561 slice: struct { start: u32, len: u32 },
544};562};
545563
546fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) !StringLiteralResult {564fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) error{ OutOfMemory, BadString }!StringLiteralResult {
547 if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } };565 if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } };
548566
549 const gpa = zg.gpa;567 const gpa = zg.gpa;
550 const string_bytes = &zg.string_bytes;568 const string_bytes = &zg.string_bytes;
551 const str_index: u32 = @intCast(zg.string_bytes.items.len);569 const str_index: u32 = @intCast(zg.string_bytes.items.len);
552 const size_hint = strLitSizeHint(zg.tree, str_node);570 const size_hint = strLitSizeHint(zg.tree, str_node);
553 try string_bytes.ensureUnusedCapacity(zg.gpa, size_hint);571 try string_bytes.ensureUnusedCapacity(gpa, size_hint);
554 switch (try parseStrLit(zg.tree, str_node, zg.string_bytes.writer(zg.gpa))) {572 const result = r: {
573 var aw: std.io.Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes);
574 defer zg.string_bytes = aw.toArrayList();
575 break :r parseStrLit(zg.tree, str_node, &aw.writer) catch |err| switch (err) {
576 error.WriteFailed => return error.OutOfMemory,
577 };
578 };
579 switch (result) {
555 .success => {},580 .success => {},
556 .failure => |err| {581 .failure => |err| {
557 const token = zg.tree.nodeMainToken(str_node);582 const token = zg.tree.nodeMainToken(str_node);
...@@ -793,10 +818,7 @@ fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.T...@@ -793,10 +818,7 @@ fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.T
793818
794fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note {819fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note {
795 const message_idx: u32 = @intCast(zg.string_bytes.items.len);820 const message_idx: u32 = @intCast(zg.string_bytes.items.len);
796 const writer = zg.string_bytes.writer(zg.gpa);821 try zg.string_bytes.print(zg.gpa, format ++ "\x00", args);
797 try writer.print(format, args);
798 try writer.writeByte(0);
799
800 return .{822 return .{
801 .msg = @enumFromInt(message_idx),823 .msg = @enumFromInt(message_idx),
802 .token = .none,824 .token = .none,
...@@ -806,10 +828,7 @@ fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, a...@@ -806,10 +828,7 @@ fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, a
806828
807fn errNoteTok(zg: *ZonGen, tok: Ast.TokenIndex, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note {829fn errNoteTok(zg: *ZonGen, tok: Ast.TokenIndex, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note {
808 const message_idx: u32 = @intCast(zg.string_bytes.items.len);830 const message_idx: u32 = @intCast(zg.string_bytes.items.len);
809 const writer = zg.string_bytes.writer(zg.gpa);831 try zg.string_bytes.print(zg.gpa, format ++ "\x00", args);
810 try writer.print(format, args);
811 try writer.writeByte(0);
812
813 return .{832 return .{
814 .msg = @enumFromInt(message_idx),833 .msg = @enumFromInt(message_idx),
815 .token = .fromToken(tok),834 .token = .fromToken(tok),
...@@ -850,9 +869,7 @@ fn addErrorInner(...@@ -850,9 +869,7 @@ fn addErrorInner(
850 try zg.error_notes.appendSlice(gpa, notes);869 try zg.error_notes.appendSlice(gpa, notes);
851870
852 const message_idx: u32 = @intCast(zg.string_bytes.items.len);871 const message_idx: u32 = @intCast(zg.string_bytes.items.len);
853 const writer = zg.string_bytes.writer(zg.gpa);872 try zg.string_bytes.print(gpa, format ++ "\x00", args);
854 try writer.print(format, args);
855 try writer.writeByte(0);
856873
857 try zg.compile_errors.append(gpa, .{874 try zg.compile_errors.append(gpa, .{
858 .msg = @enumFromInt(message_idx),875 .msg = @enumFromInt(message_idx),
...@@ -868,8 +885,9 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {...@@ -868,8 +885,9 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {
868 const tree = zg.tree;885 const tree = zg.tree;
869 assert(tree.errors.len > 0);886 assert(tree.errors.len > 0);
870887
871 var msg: std.ArrayListUnmanaged(u8) = .empty;888 var msg: std.io.Writer.Allocating = .init(gpa);
872 defer msg.deinit(gpa);889 defer msg.deinit();
890 const msg_bw = &msg.writer;
873891
874 var notes: std.ArrayListUnmanaged(Zoir.CompileError.Note) = .empty;892 var notes: std.ArrayListUnmanaged(Zoir.CompileError.Note) = .empty;
875 defer notes.deinit(gpa);893 defer notes.deinit(gpa);
...@@ -877,18 +895,20 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {...@@ -877,18 +895,20 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {
877 var cur_err = tree.errors[0];895 var cur_err = tree.errors[0];
878 for (tree.errors[1..]) |err| {896 for (tree.errors[1..]) |err| {
879 if (err.is_note) {897 if (err.is_note) {
880 try tree.renderError(err, msg.writer(gpa));898 tree.renderError(err, msg_bw) catch return error.OutOfMemory;
881 try notes.append(gpa, try zg.errNoteTok(err.token, "{s}", .{msg.items}));899 try notes.append(gpa, try zg.errNoteTok(err.token, "{s}", .{msg.getWritten()}));
882 } else {900 } else {
883 // Flush error901 // Flush error
884 try tree.renderError(cur_err, msg.writer(gpa));902 tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
885 const extra_offset = tree.errorOffset(cur_err);903 const extra_offset = tree.errorOffset(cur_err);
886 try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items);904 try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
887 notes.clearRetainingCapacity();905 notes.clearRetainingCapacity();
888 cur_err = err;906 cur_err = err;
889907
890 // TODO: `Parse` currently does not have good error recovery mechanisms, so the remaining errors could be bogus.908 // TODO: `Parse` currently does not have good error recovery
891 // As such, we'll ignore all remaining errors for now. We should improve `Parse` so that we can report all the errors.909 // mechanisms, so the remaining errors could be bogus. As such,
910 // we'll ignore all remaining errors for now. We should improve
911 // `Parse` so that we can report all the errors.
892 return;912 return;
893 }913 }
894 msg.clearRetainingCapacity();914 msg.clearRetainingCapacity();
...@@ -896,16 +916,6 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {...@@ -896,16 +916,6 @@ fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {
896916
897 // Flush error917 // Flush error
898 const extra_offset = tree.errorOffset(cur_err);918 const extra_offset = tree.errorOffset(cur_err);
899 try tree.renderError(cur_err, msg.writer(gpa));919 tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
900 try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.items}, notes.items);920 try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.getWritten()}, notes.items);
901}921}
902
903const std = @import("std");
904const assert = std.debug.assert;
905const mem = std.mem;
906const Allocator = mem.Allocator;
907const StringIndexAdapter = std.hash_map.StringIndexAdapter;
908const StringIndexContext = std.hash_map.StringIndexContext;
909const ZonGen = @This();
910const Zoir = @import("Zoir.zig");
911const Ast = @import("Ast.zig");
lib/std/zig/parser_test.zig+4-2
...@@ -6367,7 +6367,9 @@ test "ampersand" {...@@ -6367,7 +6367,9 @@ test "ampersand" {
6367var fixed_buffer_mem: [100 * 1024]u8 = undefined;6367var fixed_buffer_mem: [100 * 1024]u8 = undefined;
63686368
6369fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 {6369fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 {
6370 const stderr = std.fs.File.stderr().deprecatedWriter();6370 var buffer: [64]u8 = undefined;
6371 const stderr = std.debug.lockStderrWriter(&buffer);
6372 defer std.debug.unlockStderrWriter();
63716373
6372 var tree = try std.zig.Ast.parse(allocator, source, .zig);6374 var tree = try std.zig.Ast.parse(allocator, source, .zig);
6373 defer tree.deinit(allocator);6375 defer tree.deinit(allocator);
...@@ -6390,7 +6392,7 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *...@@ -6390,7 +6392,7 @@ fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *
6390 return error.ParseError;6392 return error.ParseError;
6391 }6393 }
63926394
6393 const formatted = try tree.render(allocator);6395 const formatted = try tree.renderAlloc(allocator);
6394 anything_changed.* = !mem.eql(u8, formatted, source);6396 anything_changed.* = !mem.eql(u8, formatted, source);
6395 return formatted;6397 return formatted;
6396}6398}
lib/std/zig/render.zig deleted-3621
...@@ -1,3621 +0,0 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const meta = std.meta;
6const Ast = std.zig.Ast;
7const Token = std.zig.Token;
8const primitives = std.zig.primitives;
9
10const indent_delta = 4;
11const asm_indent_delta = 2;
12
13pub const Error = Ast.RenderError;
14
15const Ais = AutoIndentingStream(std.ArrayList(u8).Writer);
16
17pub const Fixups = struct {
18 /// The key is the mut token (`var`/`const`) of the variable declaration
19 /// that should have a `_ = foo;` inserted afterwards.
20 unused_var_decls: std.AutoHashMapUnmanaged(Ast.TokenIndex, void) = .empty,
21 /// The functions in this unordered set of AST fn decl nodes will render
22 /// with a function body of `@trap()` instead, with all parameters
23 /// discarded.
24 gut_functions: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
25 /// These global declarations will be omitted.
26 omit_nodes: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
27 /// These expressions will be replaced with the string value.
28 replace_nodes_with_string: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
29 /// The string value will be inserted directly after the node.
30 append_string_after_node: std.AutoHashMapUnmanaged(Ast.Node.Index, []const u8) = .empty,
31 /// These nodes will be replaced with a different node.
32 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
33 /// Change all identifier names matching the key to be value instead.
34 rename_identifiers: std.StringArrayHashMapUnmanaged([]const u8) = .empty,
35
36 /// All `@import` builtin calls which refer to a file path will be prefixed
37 /// with this path.
38 rebase_imported_paths: ?[]const u8 = null,
39
40 pub fn count(f: Fixups) usize {
41 return f.unused_var_decls.count() +
42 f.gut_functions.count() +
43 f.omit_nodes.count() +
44 f.replace_nodes_with_string.count() +
45 f.append_string_after_node.count() +
46 f.replace_nodes_with_node.count() +
47 f.rename_identifiers.count() +
48 @intFromBool(f.rebase_imported_paths != null);
49 }
50
51 pub fn clearRetainingCapacity(f: *Fixups) void {
52 f.unused_var_decls.clearRetainingCapacity();
53 f.gut_functions.clearRetainingCapacity();
54 f.omit_nodes.clearRetainingCapacity();
55 f.replace_nodes_with_string.clearRetainingCapacity();
56 f.append_string_after_node.clearRetainingCapacity();
57 f.replace_nodes_with_node.clearRetainingCapacity();
58 f.rename_identifiers.clearRetainingCapacity();
59
60 f.rebase_imported_paths = null;
61 }
62
63 pub fn deinit(f: *Fixups, gpa: Allocator) void {
64 f.unused_var_decls.deinit(gpa);
65 f.gut_functions.deinit(gpa);
66 f.omit_nodes.deinit(gpa);
67 f.replace_nodes_with_string.deinit(gpa);
68 f.append_string_after_node.deinit(gpa);
69 f.replace_nodes_with_node.deinit(gpa);
70 f.rename_identifiers.deinit(gpa);
71 f.* = undefined;
72 }
73};
74
75const Render = struct {
76 gpa: Allocator,
77 ais: *Ais,
78 tree: Ast,
79 fixups: Fixups,
80};
81
82pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast, fixups: Fixups) Error!void {
83 assert(tree.errors.len == 0); // Cannot render an invalid tree.
84 var auto_indenting_stream = Ais.init(buffer, indent_delta);
85 defer auto_indenting_stream.deinit();
86 var r: Render = .{
87 .gpa = buffer.allocator,
88 .ais = &auto_indenting_stream,
89 .tree = tree,
90 .fixups = fixups,
91 };
92
93 // Render all the line comments at the beginning of the file.
94 const comment_end_loc = tree.tokenStart(0);
95 _ = try renderComments(&r, 0, comment_end_loc);
96
97 if (tree.tokenTag(0) == .container_doc_comment) {
98 try renderContainerDocComments(&r, 0);
99 }
100
101 switch (tree.mode) {
102 .zig => try renderMembers(&r, tree.rootDecls()),
103 .zon => {
104 try renderExpression(
105 &r,
106 tree.rootDecls()[0],
107 .newline,
108 );
109 },
110 }
111
112 if (auto_indenting_stream.disabled_offset) |disabled_offset| {
113 try writeFixingWhitespace(auto_indenting_stream.underlying_writer, tree.source[disabled_offset..]);
114 }
115}
116
117/// Render all members in the given slice, keeping empty lines where appropriate
118fn renderMembers(r: *Render, members: []const Ast.Node.Index) Error!void {
119 const tree = r.tree;
120 if (members.len == 0) return;
121 const container: Container = for (members) |member| {
122 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
123 } else .tuple;
124 try renderMember(r, container, members[0], .newline);
125 for (members[1..]) |member| {
126 try renderExtraNewline(r, member);
127 try renderMember(r, container, member, .newline);
128 }
129}
130
131const Container = enum {
132 @"enum",
133 tuple,
134 other,
135};
136
137fn renderMember(
138 r: *Render,
139 container: Container,
140 decl: Ast.Node.Index,
141 space: Space,
142) Error!void {
143 const tree = r.tree;
144 const ais = r.ais;
145 if (r.fixups.omit_nodes.contains(decl)) return;
146 try renderDocComments(r, tree.firstToken(decl));
147 switch (tree.nodeTag(decl)) {
148 .fn_decl => {
149 // Some examples:
150 // pub extern "foo" fn ...
151 // export fn ...
152 const fn_proto, const body_node = tree.nodeData(decl).node_and_node;
153 const fn_token = tree.nodeMainToken(fn_proto);
154 // Go back to the first token we should render here.
155 var i = fn_token;
156 while (i > 0) {
157 i -= 1;
158 switch (tree.tokenTag(i)) {
159 .keyword_extern,
160 .keyword_export,
161 .keyword_pub,
162 .string_literal,
163 .keyword_inline,
164 .keyword_noinline,
165 => continue,
166
167 else => {
168 i += 1;
169 break;
170 },
171 }
172 }
173
174 while (i < fn_token) : (i += 1) {
175 try renderToken(r, i, .space);
176 }
177 switch (tree.nodeTag(fn_proto)) {
178 .fn_proto_one, .fn_proto => {
179 var buf: [1]Ast.Node.Index = undefined;
180 const opt_callconv_expr = if (tree.nodeTag(fn_proto) == .fn_proto_one)
181 tree.fnProtoOne(&buf, fn_proto).ast.callconv_expr
182 else
183 tree.fnProto(fn_proto).ast.callconv_expr;
184
185 // Keep in sync with logic in `renderFnProto`. Search this file for the marker PROMOTE_CALLCONV_INLINE
186 if (opt_callconv_expr.unwrap()) |callconv_expr| {
187 if (tree.nodeTag(callconv_expr) == .enum_literal) {
188 if (mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)))) {
189 try ais.writer().writeAll("inline ");
190 }
191 }
192 }
193 },
194 .fn_proto_simple, .fn_proto_multi => {},
195 else => unreachable,
196 }
197 try renderExpression(r, fn_proto, .space);
198 if (r.fixups.gut_functions.contains(decl)) {
199 try ais.pushIndent(.normal);
200 const lbrace = tree.nodeMainToken(body_node);
201 try renderToken(r, lbrace, .newline);
202 try discardAllParams(r, fn_proto);
203 try ais.writer().writeAll("@trap();");
204 ais.popIndent();
205 try ais.insertNewline();
206 try renderToken(r, tree.lastToken(body_node), space); // rbrace
207 } else if (r.fixups.unused_var_decls.count() != 0) {
208 try ais.pushIndent(.normal);
209 const lbrace = tree.nodeMainToken(body_node);
210 try renderToken(r, lbrace, .newline);
211
212 var fn_proto_buf: [1]Ast.Node.Index = undefined;
213 const full_fn_proto = tree.fullFnProto(&fn_proto_buf, fn_proto).?;
214 var it = full_fn_proto.iterate(&tree);
215 while (it.next()) |param| {
216 const name_ident = param.name_token.?;
217 assert(tree.tokenTag(name_ident) == .identifier);
218 if (r.fixups.unused_var_decls.contains(name_ident)) {
219 const w = ais.writer();
220 try w.writeAll("_ = ");
221 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
222 try w.writeAll(";\n");
223 }
224 }
225 var statements_buf: [2]Ast.Node.Index = undefined;
226 const statements = tree.blockStatements(&statements_buf, body_node).?;
227 return finishRenderBlock(r, body_node, statements, space);
228 } else {
229 return renderExpression(r, body_node, space);
230 }
231 },
232 .fn_proto_simple,
233 .fn_proto_multi,
234 .fn_proto_one,
235 .fn_proto,
236 => {
237 // Extern function prototypes are parsed as these tags.
238 // Go back to the first token we should render here.
239 const fn_token = tree.nodeMainToken(decl);
240 var i = fn_token;
241 while (i > 0) {
242 i -= 1;
243 switch (tree.tokenTag(i)) {
244 .keyword_extern,
245 .keyword_export,
246 .keyword_pub,
247 .string_literal,
248 .keyword_inline,
249 .keyword_noinline,
250 => continue,
251
252 else => {
253 i += 1;
254 break;
255 },
256 }
257 }
258 while (i < fn_token) : (i += 1) {
259 try renderToken(r, i, .space);
260 }
261 try renderExpression(r, decl, .none);
262 return renderToken(r, tree.lastToken(decl) + 1, space); // semicolon
263 },
264
265 .global_var_decl,
266 .local_var_decl,
267 .simple_var_decl,
268 .aligned_var_decl,
269 => {
270 try ais.pushSpace(.semicolon);
271 try renderVarDecl(r, tree.fullVarDecl(decl).?, false, .semicolon);
272 ais.popSpace();
273 },
274
275 .test_decl => {
276 const test_token = tree.nodeMainToken(decl);
277 const opt_name_token, const block_node = tree.nodeData(decl).opt_token_and_node;
278 try renderToken(r, test_token, .space);
279 if (opt_name_token.unwrap()) |name_token| {
280 switch (tree.tokenTag(name_token)) {
281 .string_literal => try renderToken(r, name_token, .space),
282 .identifier => try renderIdentifier(r, name_token, .space, .preserve_when_shadowing),
283 else => unreachable,
284 }
285 }
286 try renderExpression(r, block_node, space);
287 },
288
289 .container_field_init,
290 .container_field_align,
291 .container_field,
292 => return renderContainerField(r, container, tree.fullContainerField(decl).?, space),
293
294 .@"comptime" => return renderExpression(r, decl, space),
295
296 .root => unreachable,
297 else => unreachable,
298 }
299}
300
301/// Render all expressions in the slice, keeping empty lines where appropriate
302fn renderExpressions(r: *Render, expressions: []const Ast.Node.Index, space: Space) Error!void {
303 if (expressions.len == 0) return;
304 try renderExpression(r, expressions[0], space);
305 for (expressions[1..]) |expression| {
306 try renderExtraNewline(r, expression);
307 try renderExpression(r, expression, space);
308 }
309}
310
311fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
312 const tree = r.tree;
313 const ais = r.ais;
314 if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
315 try ais.writer().writeAll(replacement);
316 try renderOnlySpace(r, space);
317 return;
318 } else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
319 return renderExpression(r, replacement, space);
320 }
321 switch (tree.nodeTag(node)) {
322 .identifier => {
323 const token_index = tree.nodeMainToken(node);
324 return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
325 },
326
327 .number_literal,
328 .char_literal,
329 .unreachable_literal,
330 .anyframe_literal,
331 .string_literal,
332 => return renderToken(r, tree.nodeMainToken(node), space),
333
334 .multiline_string_literal => {
335 try ais.maybeInsertNewline();
336
337 const first_tok, const last_tok = tree.nodeData(node).token_and_token;
338 for (first_tok..last_tok + 1) |i| {
339 try renderToken(r, @intCast(i), .newline);
340 }
341
342 const next_token = last_tok + 1;
343 const next_token_tag = tree.tokenTag(next_token);
344
345 // dedent the next thing that comes after a multiline string literal
346 if (!ais.indentStackEmpty() and
347 next_token_tag != .colon and
348 ((next_token_tag != .semicolon and next_token_tag != .comma) or
349 ais.lastSpaceModeIndent() < ais.currentIndent()))
350 {
351 ais.popIndent();
352 try ais.pushIndent(.normal);
353 }
354
355 switch (space) {
356 .none, .space, .newline, .skip => {},
357 .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
358 .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
359 .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space),
360 }
361 },
362
363 .error_value => {
364 const main_token = tree.nodeMainToken(node);
365 try renderToken(r, main_token, .none);
366 try renderToken(r, main_token + 1, .none);
367 return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
368 },
369
370 .block_two,
371 .block_two_semicolon,
372 .block,
373 .block_semicolon,
374 => {
375 var buf: [2]Ast.Node.Index = undefined;
376 const statements = tree.blockStatements(&buf, node).?;
377 return renderBlock(r, node, statements, space);
378 },
379
380 .@"errdefer" => {
381 const defer_token = tree.nodeMainToken(node);
382 const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;
383
384 try renderToken(r, defer_token, .space);
385 if (maybe_payload_token.unwrap()) |payload_token| {
386 try renderToken(r, payload_token - 1, .none); // |
387 try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
388 try renderToken(r, payload_token + 1, .space); // |
389 }
390 return renderExpression(r, expr, space);
391 },
392
393 .@"defer",
394 .@"comptime",
395 .@"nosuspend",
396 .@"suspend",
397 => {
398 const main_token = tree.nodeMainToken(node);
399 const item = tree.nodeData(node).node;
400 try renderToken(r, main_token, .space);
401 return renderExpression(r, item, space);
402 },
403
404 .@"catch" => {
405 const main_token = tree.nodeMainToken(node);
406 const lhs, const rhs = tree.nodeData(node).node_and_node;
407 const fallback_first = tree.firstToken(rhs);
408
409 const same_line = tree.tokensOnSameLine(main_token, fallback_first);
410 const after_op_space = if (same_line) Space.space else Space.newline;
411
412 try renderExpression(r, lhs, .space); // target
413
414 try ais.pushIndent(.normal);
415 if (tree.tokenTag(fallback_first - 1) == .pipe) {
416 try renderToken(r, main_token, .space); // catch keyword
417 try renderToken(r, main_token + 1, .none); // pipe
418 try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
419 try renderToken(r, main_token + 3, after_op_space); // pipe
420 } else {
421 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
422 try renderToken(r, main_token, after_op_space); // catch keyword
423 }
424 try renderExpression(r, rhs, space); // fallback
425 ais.popIndent();
426 },
427
428 .field_access => {
429 const lhs, const name_token = tree.nodeData(node).node_and_token;
430 const dot_token = name_token - 1;
431
432 try ais.pushIndent(.field_access);
433 try renderExpression(r, lhs, .none);
434
435 // Allow a line break between the lhs and the dot if the lhs and rhs
436 // are on different lines.
437 const lhs_last_token = tree.lastToken(lhs);
438 const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
439 if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline();
440
441 try renderToken(r, dot_token, .none);
442
443 try renderIdentifier(r, name_token, space, .eagerly_unquote); // field
444 ais.popIndent();
445 },
446
447 .error_union,
448 .switch_range,
449 => {
450 const lhs, const rhs = tree.nodeData(node).node_and_node;
451 try renderExpression(r, lhs, .none);
452 try renderToken(r, tree.nodeMainToken(node), .none);
453 return renderExpression(r, rhs, space);
454 },
455 .for_range => {
456 const start, const opt_end = tree.nodeData(node).node_and_opt_node;
457 try renderExpression(r, start, .none);
458 if (opt_end.unwrap()) |end| {
459 try renderToken(r, tree.nodeMainToken(node), .none);
460 return renderExpression(r, end, space);
461 } else {
462 return renderToken(r, tree.nodeMainToken(node), space);
463 }
464 },
465
466 .assign,
467 .assign_bit_and,
468 .assign_bit_or,
469 .assign_shl,
470 .assign_shl_sat,
471 .assign_shr,
472 .assign_bit_xor,
473 .assign_div,
474 .assign_sub,
475 .assign_sub_wrap,
476 .assign_sub_sat,
477 .assign_mod,
478 .assign_add,
479 .assign_add_wrap,
480 .assign_add_sat,
481 .assign_mul,
482 .assign_mul_wrap,
483 .assign_mul_sat,
484 => {
485 const lhs, const rhs = tree.nodeData(node).node_and_node;
486 try renderExpression(r, lhs, .space);
487 const op_token = tree.nodeMainToken(node);
488 try ais.pushIndent(.after_equals);
489 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
490 try renderToken(r, op_token, .space);
491 } else {
492 try renderToken(r, op_token, .newline);
493 }
494 try renderExpression(r, rhs, space);
495 ais.popIndent();
496 },
497
498 .add,
499 .add_wrap,
500 .add_sat,
501 .array_cat,
502 .array_mult,
503 .bang_equal,
504 .bit_and,
505 .bit_or,
506 .shl,
507 .shl_sat,
508 .shr,
509 .bit_xor,
510 .bool_and,
511 .bool_or,
512 .div,
513 .equal_equal,
514 .greater_or_equal,
515 .greater_than,
516 .less_or_equal,
517 .less_than,
518 .merge_error_sets,
519 .mod,
520 .mul,
521 .mul_wrap,
522 .mul_sat,
523 .sub,
524 .sub_wrap,
525 .sub_sat,
526 .@"orelse",
527 => {
528 const lhs, const rhs = tree.nodeData(node).node_and_node;
529 try renderExpression(r, lhs, .space);
530 const op_token = tree.nodeMainToken(node);
531 try ais.pushIndent(.binop);
532 if (tree.tokensOnSameLine(op_token, op_token + 1)) {
533 try renderToken(r, op_token, .space);
534 } else {
535 try renderToken(r, op_token, .newline);
536 }
537 try renderExpression(r, rhs, space);
538 ais.popIndent();
539 },
540
541 .assign_destructure => {
542 const full = tree.assignDestructure(node);
543 if (full.comptime_token) |comptime_token| {
544 try renderToken(r, comptime_token, .space);
545 }
546
547 for (full.ast.variables, 0..) |variable_node, i| {
548 const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space;
549 switch (tree.nodeTag(variable_node)) {
550 .global_var_decl,
551 .local_var_decl,
552 .simple_var_decl,
553 .aligned_var_decl,
554 => {
555 try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
556 },
557 else => try renderExpression(r, variable_node, variable_space),
558 }
559 }
560 try ais.pushIndent(.after_equals);
561 if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
562 try renderToken(r, full.ast.equal_token, .space);
563 } else {
564 try renderToken(r, full.ast.equal_token, .newline);
565 }
566 try renderExpression(r, full.ast.value_expr, space);
567 ais.popIndent();
568 },
569
570 .bit_not,
571 .bool_not,
572 .negation,
573 .negation_wrap,
574 .optional_type,
575 .address_of,
576 => {
577 try renderToken(r, tree.nodeMainToken(node), .none);
578 return renderExpression(r, tree.nodeData(node).node, space);
579 },
580
581 .@"try",
582 .@"resume",
583 => {
584 try renderToken(r, tree.nodeMainToken(node), .space);
585 return renderExpression(r, tree.nodeData(node).node, space);
586 },
587
588 .array_type,
589 .array_type_sentinel,
590 => return renderArrayType(r, tree.fullArrayType(node).?, space),
591
592 .ptr_type_aligned,
593 .ptr_type_sentinel,
594 .ptr_type,
595 .ptr_type_bit_range,
596 => return renderPtrType(r, tree.fullPtrType(node).?, space),
597
598 .array_init_one,
599 .array_init_one_comma,
600 .array_init_dot_two,
601 .array_init_dot_two_comma,
602 .array_init_dot,
603 .array_init_dot_comma,
604 .array_init,
605 .array_init_comma,
606 => {
607 var elements: [2]Ast.Node.Index = undefined;
608 return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
609 },
610
611 .struct_init_one,
612 .struct_init_one_comma,
613 .struct_init_dot_two,
614 .struct_init_dot_two_comma,
615 .struct_init_dot,
616 .struct_init_dot_comma,
617 .struct_init,
618 .struct_init_comma,
619 => {
620 var buf: [2]Ast.Node.Index = undefined;
621 return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
622 },
623
624 .call_one,
625 .call_one_comma,
626 .call,
627 .call_comma,
628 => {
629 var buf: [1]Ast.Node.Index = undefined;
630 return renderCall(r, tree.fullCall(&buf, node).?, space);
631 },
632
633 .array_access => {
634 const lhs, const rhs = tree.nodeData(node).node_and_node;
635 const lbracket = tree.firstToken(rhs) - 1;
636 const rbracket = tree.lastToken(rhs) + 1;
637 const one_line = tree.tokensOnSameLine(lbracket, rbracket);
638 const inner_space = if (one_line) Space.none else Space.newline;
639 try renderExpression(r, lhs, .none);
640 try ais.pushIndent(.normal);
641 try renderToken(r, lbracket, inner_space); // [
642 try renderExpression(r, rhs, inner_space);
643 ais.popIndent();
644 return renderToken(r, rbracket, space); // ]
645 },
646
647 .slice_open,
648 .slice,
649 .slice_sentinel,
650 => return renderSlice(r, node, tree.fullSlice(node).?, space),
651
652 .deref => {
653 try renderExpression(r, tree.nodeData(node).node, .none);
654 return renderToken(r, tree.nodeMainToken(node), space);
655 },
656
657 .unwrap_optional => {
658 const lhs, const question_mark = tree.nodeData(node).node_and_token;
659 const dot_token = question_mark - 1;
660 try renderExpression(r, lhs, .none);
661 try renderToken(r, dot_token, .none);
662 return renderToken(r, question_mark, space);
663 },
664
665 .@"break", .@"continue" => {
666 const main_token = tree.nodeMainToken(node);
667 const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
668 if (opt_label_token == .none and opt_target == .none) {
669 try renderToken(r, main_token, space); // break/continue
670 } else if (opt_label_token == .none and opt_target != .none) {
671 const target = opt_target.unwrap().?;
672 try renderToken(r, main_token, .space); // break/continue
673 try renderExpression(r, target, space);
674 } else if (opt_label_token != .none and opt_target == .none) {
675 const label_token = opt_label_token.unwrap().?;
676 try renderToken(r, main_token, .space); // break/continue
677 try renderToken(r, label_token - 1, .none); // :
678 try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
679 } else if (opt_label_token != .none and opt_target != .none) {
680 const label_token = opt_label_token.unwrap().?;
681 const target = opt_target.unwrap().?;
682 try renderToken(r, main_token, .space); // break/continue
683 try renderToken(r, label_token - 1, .none); // :
684 try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
685 try renderExpression(r, target, space);
686 } else unreachable;
687 },
688
689 .@"return" => {
690 if (tree.nodeData(node).opt_node.unwrap()) |expr| {
691 try renderToken(r, tree.nodeMainToken(node), .space);
692 try renderExpression(r, expr, space);
693 } else {
694 try renderToken(r, tree.nodeMainToken(node), space);
695 }
696 },
697
698 .grouped_expression => {
699 const expr, const rparen = tree.nodeData(node).node_and_token;
700 try ais.pushIndent(.normal);
701 try renderToken(r, tree.nodeMainToken(node), .none); // lparen
702 try renderExpression(r, expr, .none);
703 ais.popIndent();
704 return renderToken(r, rparen, space);
705 },
706
707 .container_decl,
708 .container_decl_trailing,
709 .container_decl_arg,
710 .container_decl_arg_trailing,
711 .container_decl_two,
712 .container_decl_two_trailing,
713 .tagged_union,
714 .tagged_union_trailing,
715 .tagged_union_enum_tag,
716 .tagged_union_enum_tag_trailing,
717 .tagged_union_two,
718 .tagged_union_two_trailing,
719 => {
720 var buf: [2]Ast.Node.Index = undefined;
721 return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
722 },
723
724 .error_set_decl => {
725 const error_token = tree.nodeMainToken(node);
726 const lbrace, const rbrace = tree.nodeData(node).token_and_token;
727
728 try renderToken(r, error_token, .none);
729
730 if (lbrace + 1 == rbrace) {
731 // There is nothing between the braces so render condensed: `error{}`
732 try renderToken(r, lbrace, .none);
733 return renderToken(r, rbrace, space);
734 } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
735 // There is exactly one member and no trailing comma or
736 // comments, so render without surrounding spaces: `error{Foo}`
737 try renderToken(r, lbrace, .none);
738 try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
739 return renderToken(r, rbrace, space);
740 } else if (tree.tokenTag(rbrace - 1) == .comma) {
741 // There is a trailing comma so render each member on a new line.
742 try ais.pushIndent(.normal);
743 try renderToken(r, lbrace, .newline);
744 var i = lbrace + 1;
745 while (i < rbrace) : (i += 1) {
746 if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
747 switch (tree.tokenTag(i)) {
748 .doc_comment => try renderToken(r, i, .newline),
749 .identifier => {
750 try ais.pushSpace(.comma);
751 try renderIdentifier(r, i, .comma, .eagerly_unquote);
752 ais.popSpace();
753 },
754 .comma => {},
755 else => unreachable,
756 }
757 }
758 ais.popIndent();
759 return renderToken(r, rbrace, space);
760 } else {
761 // There is no trailing comma so render everything on one line.
762 try renderToken(r, lbrace, .space);
763 var i = lbrace + 1;
764 while (i < rbrace) : (i += 1) {
765 switch (tree.tokenTag(i)) {
766 .doc_comment => unreachable, // TODO
767 .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
768 .comma => {},
769 else => unreachable,
770 }
771 }
772 return renderToken(r, rbrace, space);
773 }
774 },
775
776 .builtin_call_two,
777 .builtin_call_two_comma,
778 .builtin_call,
779 .builtin_call_comma,
780 => {
781 var buf: [2]Ast.Node.Index = undefined;
782 const params = tree.builtinCallParams(&buf, node).?;
783 return renderBuiltinCall(r, tree.nodeMainToken(node), params, space);
784 },
785
786 .fn_proto_simple,
787 .fn_proto_multi,
788 .fn_proto_one,
789 .fn_proto,
790 => {
791 var buf: [1]Ast.Node.Index = undefined;
792 return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
793 },
794
795 .anyframe_type => {
796 const main_token = tree.nodeMainToken(node);
797 try renderToken(r, main_token, .none); // anyframe
798 try renderToken(r, main_token + 1, .none); // ->
799 return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
800 },
801
802 .@"switch",
803 .switch_comma,
804 => {
805 const full = tree.switchFull(node);
806
807 if (full.label_token) |label_token| {
808 try renderIdentifier(r, label_token, .none, .eagerly_unquote); // label
809 try renderToken(r, label_token + 1, .space); // :
810 }
811
812 const rparen = tree.lastToken(full.ast.condition) + 1;
813
814 try renderToken(r, full.ast.switch_token, .space); // switch
815 try renderToken(r, full.ast.switch_token + 1, .none); // (
816 try renderExpression(r, full.ast.condition, .none); // condition expression
817 try renderToken(r, rparen, .space); // )
818
819 try ais.pushIndent(.normal);
820 if (full.ast.cases.len == 0) {
821 try renderToken(r, rparen + 1, .none); // {
822 } else {
823 try renderToken(r, rparen + 1, .newline); // {
824 try ais.pushSpace(.comma);
825 try renderExpressions(r, full.ast.cases, .comma);
826 ais.popSpace();
827 }
828 ais.popIndent();
829 return renderToken(r, tree.lastToken(node), space); // }
830 },
831
832 .switch_case_one,
833 .switch_case_inline_one,
834 .switch_case,
835 .switch_case_inline,
836 => return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
837
838 .while_simple,
839 .while_cont,
840 .@"while",
841 => return renderWhile(r, tree.fullWhile(node).?, space),
842
843 .for_simple,
844 .@"for",
845 => return renderFor(r, tree.fullFor(node).?, space),
846
847 .if_simple,
848 .@"if",
849 => return renderIf(r, tree.fullIf(node).?, space),
850
851 .asm_simple,
852 .@"asm",
853 => return renderAsm(r, tree.fullAsm(node).?, space),
854
855 // To be removed after 0.15.0 is tagged
856 .asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),
857
858 .enum_literal => {
859 try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
860 return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
861 },
862
863 .fn_decl => unreachable,
864 .container_field => unreachable,
865 .container_field_init => unreachable,
866 .container_field_align => unreachable,
867 .root => unreachable,
868 .global_var_decl => unreachable,
869 .local_var_decl => unreachable,
870 .simple_var_decl => unreachable,
871 .aligned_var_decl => unreachable,
872 .test_decl => unreachable,
873 .asm_output => unreachable,
874 .asm_input => unreachable,
875 }
876}
877
878/// Same as `renderExpression`, but afterwards looks for any
879/// append_string_after_node fixups to apply
880fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
881 const ais = r.ais;
882 try renderExpression(r, node, space);
883 if (r.fixups.append_string_after_node.get(node)) |bytes| {
884 try ais.writer().writeAll(bytes);
885 }
886}
887
888fn renderArrayType(
889 r: *Render,
890 array_type: Ast.full.ArrayType,
891 space: Space,
892) Error!void {
893 const tree = r.tree;
894 const ais = r.ais;
895 const rbracket = tree.firstToken(array_type.ast.elem_type) - 1;
896 const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket);
897 const inner_space = if (one_line) Space.none else Space.newline;
898 try ais.pushIndent(.normal);
899 try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket
900 try renderExpression(r, array_type.ast.elem_count, inner_space);
901 if (array_type.ast.sentinel.unwrap()) |sentinel| {
902 try renderToken(r, tree.firstToken(sentinel) - 1, inner_space); // colon
903 try renderExpression(r, sentinel, inner_space);
904 }
905 ais.popIndent();
906 try renderToken(r, rbracket, .none); // rbracket
907 return renderExpression(r, array_type.ast.elem_type, space);
908}
909
910fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void {
911 const tree = r.tree;
912 const main_token = ptr_type.ast.main_token;
913 switch (ptr_type.size) {
914 .one => {
915 // Since ** tokens exist and the same token is shared by two
916 // nested pointer types, we check to see if we are the parent
917 // in such a relationship. If so, skip rendering anything for
918 // this pointer type and rely on the child to render our asterisk
919 // as well when it renders the ** token.
920 if (tree.tokenTag(main_token) == .asterisk_asterisk and
921 main_token == tree.nodeMainToken(ptr_type.ast.child_type))
922 {
923 return renderExpression(r, ptr_type.ast.child_type, space);
924 }
925 try renderToken(r, main_token, .none); // asterisk
926 },
927 .many => {
928 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
929 try renderToken(r, main_token, .none); // lbracket
930 try renderToken(r, main_token + 1, .none); // asterisk
931 try renderToken(r, main_token + 2, .none); // colon
932 try renderExpression(r, sentinel, .none);
933 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
934 } else {
935 try renderToken(r, main_token, .none); // lbracket
936 try renderToken(r, main_token + 1, .none); // asterisk
937 try renderToken(r, main_token + 2, .none); // rbracket
938 }
939 },
940 .c => {
941 try renderToken(r, main_token, .none); // lbracket
942 try renderToken(r, main_token + 1, .none); // asterisk
943 try renderToken(r, main_token + 2, .none); // c
944 try renderToken(r, main_token + 3, .none); // rbracket
945 },
946 .slice => {
947 if (ptr_type.ast.sentinel.unwrap()) |sentinel| {
948 try renderToken(r, main_token, .none); // lbracket
949 try renderToken(r, main_token + 1, .none); // colon
950 try renderExpression(r, sentinel, .none);
951 try renderToken(r, tree.lastToken(sentinel) + 1, .none); // rbracket
952 } else {
953 try renderToken(r, main_token, .none); // lbracket
954 try renderToken(r, main_token + 1, .none); // rbracket
955 }
956 },
957 }
958
959 if (ptr_type.allowzero_token) |allowzero_token| {
960 try renderToken(r, allowzero_token, .space);
961 }
962
963 if (ptr_type.ast.align_node.unwrap()) |align_node| {
964 const align_first = tree.firstToken(align_node);
965 try renderToken(r, align_first - 2, .none); // align
966 try renderToken(r, align_first - 1, .none); // lparen
967 try renderExpression(r, align_node, .none);
968 if (ptr_type.ast.bit_range_start.unwrap()) |bit_range_start| {
969 const bit_range_end = ptr_type.ast.bit_range_end.unwrap().?;
970 try renderToken(r, tree.firstToken(bit_range_start) - 1, .none); // colon
971 try renderExpression(r, bit_range_start, .none);
972 try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon
973 try renderExpression(r, bit_range_end, .none);
974 try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen
975 } else {
976 try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen
977 }
978 }
979
980 if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| {
981 const addrspace_first = tree.firstToken(addrspace_node);
982 try renderToken(r, addrspace_first - 2, .none); // addrspace
983 try renderToken(r, addrspace_first - 1, .none); // lparen
984 try renderExpression(r, addrspace_node, .none);
985 try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen
986 }
987
988 if (ptr_type.const_token) |const_token| {
989 try renderToken(r, const_token, .space);
990 }
991
992 if (ptr_type.volatile_token) |volatile_token| {
993 try renderToken(r, volatile_token, .space);
994 }
995
996 try renderExpression(r, ptr_type.ast.child_type, space);
997}
998
999fn renderSlice(
1000 r: *Render,
1001 slice_node: Ast.Node.Index,
1002 slice: Ast.full.Slice,
1003 space: Space,
1004) Error!void {
1005 const tree = r.tree;
1006 const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or
1007 if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false;
1008 const after_start_space = if (after_start_space_bool) Space.space else Space.none;
1009 const after_dots_space = if (slice.ast.end != .none)
1010 after_start_space
1011 else if (slice.ast.sentinel != .none) Space.space else Space.none;
1012
1013 try renderExpression(r, slice.ast.sliced, .none);
1014 try renderToken(r, slice.ast.lbracket, .none); // lbracket
1015
1016 const start_last = tree.lastToken(slice.ast.start);
1017 try renderExpression(r, slice.ast.start, after_start_space);
1018 try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..")
1019
1020 if (slice.ast.end.unwrap()) |end| {
1021 const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none;
1022 try renderExpression(r, end, after_end_space);
1023 }
1024
1025 if (slice.ast.sentinel.unwrap()) |sentinel| {
1026 try renderToken(r, tree.firstToken(sentinel) - 1, .none); // colon
1027 try renderExpression(r, sentinel, .none);
1028 }
1029
1030 try renderToken(r, tree.lastToken(slice_node), space); // rbracket
1031}
1032
1033fn renderAsmOutput(
1034 r: *Render,
1035 asm_output: Ast.Node.Index,
1036 space: Space,
1037) Error!void {
1038 const tree = r.tree;
1039 assert(tree.nodeTag(asm_output) == .asm_output);
1040 const symbolic_name = tree.nodeMainToken(asm_output);
1041
1042 try renderToken(r, symbolic_name - 1, .none); // lbracket
1043 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1044 try renderToken(r, symbolic_name + 1, .space); // rbracket
1045 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1046 try renderToken(r, symbolic_name + 3, .none); // lparen
1047
1048 if (tree.tokenTag(symbolic_name + 4) == .arrow) {
1049 const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token;
1050 try renderToken(r, symbolic_name + 4, .space); // ->
1051 try renderExpression(r, type_expr.unwrap().?, Space.none);
1052 return renderToken(r, rparen, space);
1053 } else {
1054 try renderIdentifier(r, symbolic_name + 4, .none, .eagerly_unquote); // ident
1055 return renderToken(r, symbolic_name + 5, space); // rparen
1056 }
1057}
1058
1059fn renderAsmInput(
1060 r: *Render,
1061 asm_input: Ast.Node.Index,
1062 space: Space,
1063) Error!void {
1064 const tree = r.tree;
1065 assert(tree.nodeTag(asm_input) == .asm_input);
1066 const symbolic_name = tree.nodeMainToken(asm_input);
1067 const expr, const rparen = tree.nodeData(asm_input).node_and_token;
1068
1069 try renderToken(r, symbolic_name - 1, .none); // lbracket
1070 try renderIdentifier(r, symbolic_name, .none, .eagerly_unquote); // ident
1071 try renderToken(r, symbolic_name + 1, .space); // rbracket
1072 try renderToken(r, symbolic_name + 2, .space); // "constraint"
1073 try renderToken(r, symbolic_name + 3, .none); // lparen
1074 try renderExpression(r, expr, Space.none);
1075 return renderToken(r, rparen, space);
1076}
1077
1078fn renderVarDecl(
1079 r: *Render,
1080 var_decl: Ast.full.VarDecl,
1081 /// Destructures intentionally ignore leading `comptime` tokens.
1082 ignore_comptime_token: bool,
1083 /// `comma_space` and `space` are used for destructure LHS decls.
1084 space: Space,
1085) Error!void {
1086 try renderVarDeclWithoutFixups(r, var_decl, ignore_comptime_token, space);
1087 if (r.fixups.unused_var_decls.contains(var_decl.ast.mut_token + 1)) {
1088 // Discard the variable like this: `_ = foo;`
1089 const w = r.ais.writer();
1090 try w.writeAll("_ = ");
1091 try w.writeAll(tokenSliceForRender(r.tree, var_decl.ast.mut_token + 1));
1092 try w.writeAll(";\n");
1093 }
1094}
1095
1096fn renderVarDeclWithoutFixups(
1097 r: *Render,
1098 var_decl: Ast.full.VarDecl,
1099 /// Destructures intentionally ignore leading `comptime` tokens.
1100 ignore_comptime_token: bool,
1101 /// `comma_space` and `space` are used for destructure LHS decls.
1102 space: Space,
1103) Error!void {
1104 const tree = r.tree;
1105 const ais = r.ais;
1106
1107 if (var_decl.visib_token) |visib_token| {
1108 try renderToken(r, visib_token, Space.space); // pub
1109 }
1110
1111 if (var_decl.extern_export_token) |extern_export_token| {
1112 try renderToken(r, extern_export_token, Space.space); // extern
1113
1114 if (var_decl.lib_name) |lib_name| {
1115 try renderToken(r, lib_name, Space.space); // "lib"
1116 }
1117 }
1118
1119 if (var_decl.threadlocal_token) |thread_local_token| {
1120 try renderToken(r, thread_local_token, Space.space); // threadlocal
1121 }
1122
1123 if (!ignore_comptime_token) {
1124 if (var_decl.comptime_token) |comptime_token| {
1125 try renderToken(r, comptime_token, Space.space); // comptime
1126 }
1127 }
1128
1129 try renderToken(r, var_decl.ast.mut_token, .space); // var
1130
1131 if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or
1132 var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1133 var_decl.ast.init_node != .none)
1134 {
1135 const name_space = if (var_decl.ast.type_node == .none and
1136 (var_decl.ast.align_node != .none or
1137 var_decl.ast.addrspace_node != .none or
1138 var_decl.ast.section_node != .none or
1139 var_decl.ast.init_node != .none))
1140 Space.space
1141 else
1142 Space.none;
1143
1144 try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name
1145 } else {
1146 return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name
1147 }
1148
1149 if (var_decl.ast.type_node.unwrap()) |type_node| {
1150 try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // :
1151 if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or
1152 var_decl.ast.section_node != .none or var_decl.ast.init_node != .none)
1153 {
1154 try renderExpression(r, type_node, .space);
1155 } else {
1156 return renderExpression(r, type_node, space);
1157 }
1158 }
1159
1160 if (var_decl.ast.align_node.unwrap()) |align_node| {
1161 const lparen = tree.firstToken(align_node) - 1;
1162 const align_kw = lparen - 1;
1163 const rparen = tree.lastToken(align_node) + 1;
1164 try renderToken(r, align_kw, Space.none); // align
1165 try renderToken(r, lparen, Space.none); // (
1166 try renderExpression(r, align_node, Space.none);
1167 if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or
1168 var_decl.ast.init_node != .none)
1169 {
1170 try renderToken(r, rparen, .space); // )
1171 } else {
1172 return renderToken(r, rparen, space); // )
1173 }
1174 }
1175
1176 if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| {
1177 const lparen = tree.firstToken(addrspace_node) - 1;
1178 const addrspace_kw = lparen - 1;
1179 const rparen = tree.lastToken(addrspace_node) + 1;
1180 try renderToken(r, addrspace_kw, Space.none); // addrspace
1181 try renderToken(r, lparen, Space.none); // (
1182 try renderExpression(r, addrspace_node, Space.none);
1183 if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) {
1184 try renderToken(r, rparen, .space); // )
1185 } else {
1186 try renderToken(r, rparen, .none); // )
1187 return renderToken(r, rparen + 1, Space.newline); // ;
1188 }
1189 }
1190
1191 if (var_decl.ast.section_node.unwrap()) |section_node| {
1192 const lparen = tree.firstToken(section_node) - 1;
1193 const section_kw = lparen - 1;
1194 const rparen = tree.lastToken(section_node) + 1;
1195 try renderToken(r, section_kw, Space.none); // linksection
1196 try renderToken(r, lparen, Space.none); // (
1197 try renderExpression(r, section_node, Space.none);
1198 if (var_decl.ast.init_node != .none) {
1199 try renderToken(r, rparen, .space); // )
1200 } else {
1201 return renderToken(r, rparen, space); // )
1202 }
1203 }
1204
1205 const init_node = var_decl.ast.init_node.unwrap().?;
1206
1207 const eq_token = tree.firstToken(init_node) - 1;
1208 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1209 try ais.pushIndent(.after_equals);
1210 try renderToken(r, eq_token, eq_space); // =
1211 try renderExpression(r, init_node, space); // ;
1212 ais.popIndent();
1213}
1214
1215fn renderIf(r: *Render, if_node: Ast.full.If, space: Space) Error!void {
1216 return renderWhile(r, .{
1217 .ast = .{
1218 .while_token = if_node.ast.if_token,
1219 .cond_expr = if_node.ast.cond_expr,
1220 .cont_expr = .none,
1221 .then_expr = if_node.ast.then_expr,
1222 .else_expr = if_node.ast.else_expr,
1223 },
1224 .inline_token = null,
1225 .label_token = null,
1226 .payload_token = if_node.payload_token,
1227 .else_token = if_node.else_token,
1228 .error_token = if_node.error_token,
1229 }, space);
1230}
1231
1232/// Note that this function is additionally used to render if expressions, with
1233/// respective values set to null.
1234fn renderWhile(r: *Render, while_node: Ast.full.While, space: Space) Error!void {
1235 const tree = r.tree;
1236
1237 if (while_node.label_token) |label| {
1238 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1239 try renderToken(r, label + 1, .space); // :
1240 }
1241
1242 if (while_node.inline_token) |inline_token| {
1243 try renderToken(r, inline_token, .space); // inline
1244 }
1245
1246 try renderToken(r, while_node.ast.while_token, .space); // if/for/while
1247 try renderToken(r, while_node.ast.while_token + 1, .none); // lparen
1248 try renderExpression(r, while_node.ast.cond_expr, .none); // condition
1249
1250 var last_prefix_token = tree.lastToken(while_node.ast.cond_expr) + 1; // rparen
1251
1252 if (while_node.payload_token) |payload_token| {
1253 try renderToken(r, last_prefix_token, .space);
1254 try renderToken(r, payload_token - 1, .none); // |
1255 const ident = blk: {
1256 if (tree.tokenTag(payload_token) == .asterisk) {
1257 try renderToken(r, payload_token, .none); // *
1258 break :blk payload_token + 1;
1259 } else {
1260 break :blk payload_token;
1261 }
1262 };
1263 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1264 const pipe = blk: {
1265 if (tree.tokenTag(ident + 1) == .comma) {
1266 try renderToken(r, ident + 1, .space); // ,
1267 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // index
1268 break :blk ident + 3;
1269 } else {
1270 break :blk ident + 1;
1271 }
1272 };
1273 last_prefix_token = pipe;
1274 }
1275
1276 if (while_node.ast.cont_expr.unwrap()) |cont_expr| {
1277 try renderToken(r, last_prefix_token, .space);
1278 const lparen = tree.firstToken(cont_expr) - 1;
1279 try renderToken(r, lparen - 1, .space); // :
1280 try renderToken(r, lparen, .none); // lparen
1281 try renderExpression(r, cont_expr, .none);
1282 last_prefix_token = tree.lastToken(cont_expr) + 1; // rparen
1283 }
1284
1285 try renderThenElse(
1286 r,
1287 last_prefix_token,
1288 while_node.ast.then_expr,
1289 while_node.else_token,
1290 while_node.error_token,
1291 while_node.ast.else_expr,
1292 space,
1293 );
1294}
1295
1296fn renderThenElse(
1297 r: *Render,
1298 last_prefix_token: Ast.TokenIndex,
1299 then_expr: Ast.Node.Index,
1300 else_token: ?Ast.TokenIndex,
1301 maybe_error_token: ?Ast.TokenIndex,
1302 opt_else_expr: Ast.Node.OptionalIndex,
1303 space: Space,
1304) Error!void {
1305 const tree = r.tree;
1306 const ais = r.ais;
1307 const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr));
1308 const indent_then_expr = !then_expr_is_block and
1309 !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr));
1310
1311 if (indent_then_expr) try ais.pushIndent(.normal);
1312
1313 if (then_expr_is_block and ais.isLineOverIndented()) {
1314 ais.disableIndentCommitting();
1315 try renderToken(r, last_prefix_token, .newline);
1316 ais.enableIndentCommitting();
1317 } else if (indent_then_expr) {
1318 try renderToken(r, last_prefix_token, .newline);
1319 } else {
1320 try renderToken(r, last_prefix_token, .space);
1321 }
1322
1323 if (opt_else_expr.unwrap()) |else_expr| {
1324 if (indent_then_expr) {
1325 try renderExpression(r, then_expr, .newline);
1326 } else {
1327 try renderExpression(r, then_expr, .space);
1328 }
1329
1330 if (indent_then_expr) ais.popIndent();
1331
1332 var last_else_token = else_token.?;
1333
1334 if (maybe_error_token) |error_token| {
1335 try renderToken(r, last_else_token, .space); // else
1336 try renderToken(r, error_token - 1, .none); // |
1337 try renderIdentifier(r, error_token, .none, .preserve_when_shadowing); // identifier
1338 last_else_token = error_token + 1; // |
1339 }
1340
1341 const indent_else_expr = indent_then_expr and
1342 !nodeIsBlock(tree.nodeTag(else_expr)) and
1343 !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr));
1344 if (indent_else_expr) {
1345 try ais.pushIndent(.normal);
1346 try renderToken(r, last_else_token, .newline);
1347 try renderExpression(r, else_expr, space);
1348 ais.popIndent();
1349 } else {
1350 try renderToken(r, last_else_token, .space);
1351 try renderExpression(r, else_expr, space);
1352 }
1353 } else {
1354 try renderExpression(r, then_expr, space);
1355 if (indent_then_expr) ais.popIndent();
1356 }
1357}
1358
1359fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void {
1360 const tree = r.tree;
1361 const ais = r.ais;
1362 const token_tags = tree.tokens.items(.tag);
1363
1364 if (for_node.label_token) |label| {
1365 try renderIdentifier(r, label, .none, .eagerly_unquote); // label
1366 try renderToken(r, label + 1, .space); // :
1367 }
1368
1369 if (for_node.inline_token) |inline_token| {
1370 try renderToken(r, inline_token, .space); // inline
1371 }
1372
1373 try renderToken(r, for_node.ast.for_token, .space); // if/for/while
1374
1375 const lparen = for_node.ast.for_token + 1;
1376 try renderParamList(r, lparen, for_node.ast.inputs, .space);
1377
1378 var cur = for_node.payload_token;
1379 const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?;
1380 if (tree.tokenTag(@intCast(pipe - 1)) == .comma) {
1381 try ais.pushIndent(.normal);
1382 try renderToken(r, cur - 1, .newline); // |
1383 while (true) {
1384 if (tree.tokenTag(cur) == .asterisk) {
1385 try renderToken(r, cur, .none); // *
1386 cur += 1;
1387 }
1388 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1389 cur += 1;
1390 if (tree.tokenTag(cur) == .comma) {
1391 try renderToken(r, cur, .newline); // ,
1392 cur += 1;
1393 }
1394 if (tree.tokenTag(cur) == .pipe) {
1395 break;
1396 }
1397 }
1398 ais.popIndent();
1399 } else {
1400 try renderToken(r, cur - 1, .none); // |
1401 while (true) {
1402 if (tree.tokenTag(cur) == .asterisk) {
1403 try renderToken(r, cur, .none); // *
1404 cur += 1;
1405 }
1406 try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier
1407 cur += 1;
1408 if (tree.tokenTag(cur) == .comma) {
1409 try renderToken(r, cur, .space); // ,
1410 cur += 1;
1411 }
1412 if (tree.tokenTag(cur) == .pipe) {
1413 break;
1414 }
1415 }
1416 }
1417
1418 try renderThenElse(
1419 r,
1420 cur,
1421 for_node.ast.then_expr,
1422 for_node.else_token,
1423 null,
1424 for_node.ast.else_expr,
1425 space,
1426 );
1427}
1428
1429fn renderContainerField(
1430 r: *Render,
1431 container: Container,
1432 field_param: Ast.full.ContainerField,
1433 space: Space,
1434) Error!void {
1435 const tree = r.tree;
1436 const ais = r.ais;
1437 var field = field_param;
1438 if (container != .tuple) field.convertToNonTupleLike(&tree);
1439 const quote: QuoteBehavior = switch (container) {
1440 .@"enum" => .eagerly_unquote_except_underscore,
1441 .tuple, .other => .eagerly_unquote,
1442 };
1443
1444 if (field.comptime_token) |t| {
1445 try renderToken(r, t, .space); // comptime
1446 }
1447 if (field.ast.type_expr == .none and field.ast.value_expr == .none) {
1448 if (field.ast.align_expr.unwrap()) |align_expr| {
1449 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1450 const lparen_token = tree.firstToken(align_expr) - 1;
1451 const align_kw = lparen_token - 1;
1452 const rparen_token = tree.lastToken(align_expr) + 1;
1453 try renderToken(r, align_kw, .none); // align
1454 try renderToken(r, lparen_token, .none); // (
1455 try renderExpression(r, align_expr, .none); // alignment
1456 return renderToken(r, rparen_token, .space); // )
1457 }
1458 return renderIdentifierComma(r, field.ast.main_token, space, quote); // name
1459 }
1460 if (field.ast.type_expr != .none and field.ast.value_expr == .none) {
1461 const type_expr = field.ast.type_expr.unwrap().?;
1462 if (!field.ast.tuple_like) {
1463 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1464 try renderToken(r, field.ast.main_token + 1, .space); // :
1465 }
1466
1467 if (field.ast.align_expr.unwrap()) |align_expr| {
1468 try renderExpression(r, type_expr, .space); // type
1469 const align_token = tree.firstToken(align_expr) - 2;
1470 try renderToken(r, align_token, .none); // align
1471 try renderToken(r, align_token + 1, .none); // (
1472 try renderExpression(r, align_expr, .none); // alignment
1473 const rparen = tree.lastToken(align_expr) + 1;
1474 return renderTokenComma(r, rparen, space); // )
1475 } else {
1476 return renderExpressionComma(r, type_expr, space); // type
1477 }
1478 }
1479 if (field.ast.type_expr == .none and field.ast.value_expr != .none) {
1480 const value_expr = field.ast.value_expr.unwrap().?;
1481
1482 try renderIdentifier(r, field.ast.main_token, .space, quote); // name
1483 if (field.ast.align_expr.unwrap()) |align_expr| {
1484 const lparen_token = tree.firstToken(align_expr) - 1;
1485 const align_kw = lparen_token - 1;
1486 const rparen_token = tree.lastToken(align_expr) + 1;
1487 try renderToken(r, align_kw, .none); // align
1488 try renderToken(r, lparen_token, .none); // (
1489 try renderExpression(r, align_expr, .none); // alignment
1490 try renderToken(r, rparen_token, .space); // )
1491 }
1492 try renderToken(r, field.ast.main_token + 1, .space); // =
1493 return renderExpressionComma(r, value_expr, space); // value
1494 }
1495 if (!field.ast.tuple_like) {
1496 try renderIdentifier(r, field.ast.main_token, .none, quote); // name
1497 try renderToken(r, field.ast.main_token + 1, .space); // :
1498 }
1499
1500 const type_expr = field.ast.type_expr.unwrap().?;
1501 const value_expr = field.ast.value_expr.unwrap().?;
1502
1503 try renderExpression(r, type_expr, .space); // type
1504
1505 if (field.ast.align_expr.unwrap()) |align_expr| {
1506 const lparen_token = tree.firstToken(align_expr) - 1;
1507 const align_kw = lparen_token - 1;
1508 const rparen_token = tree.lastToken(align_expr) + 1;
1509 try renderToken(r, align_kw, .none); // align
1510 try renderToken(r, lparen_token, .none); // (
1511 try renderExpression(r, align_expr, .none); // alignment
1512 try renderToken(r, rparen_token, .space); // )
1513 }
1514 const eq_token = tree.firstToken(value_expr) - 1;
1515 const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline;
1516
1517 try ais.pushIndent(.after_equals);
1518 try renderToken(r, eq_token, eq_space); // =
1519
1520 if (eq_space == .space) {
1521 ais.popIndent();
1522 try renderExpressionComma(r, value_expr, space); // value
1523 return;
1524 }
1525
1526 const maybe_comma = tree.lastToken(value_expr) + 1;
1527
1528 if (tree.tokenTag(maybe_comma) == .comma) {
1529 try renderExpression(r, value_expr, .none); // value
1530 ais.popIndent();
1531 try renderToken(r, maybe_comma, .newline);
1532 } else {
1533 try renderExpression(r, value_expr, space); // value
1534 ais.popIndent();
1535 }
1536}
1537
1538fn renderBuiltinCall(
1539 r: *Render,
1540 builtin_token: Ast.TokenIndex,
1541 params: []const Ast.Node.Index,
1542 space: Space,
1543) Error!void {
1544 const tree = r.tree;
1545 const ais = r.ais;
1546
1547 try renderToken(r, builtin_token, .none); // @name
1548
1549 if (params.len == 0) {
1550 try renderToken(r, builtin_token + 1, .none); // (
1551 return renderToken(r, builtin_token + 2, space); // )
1552 }
1553
1554 if (r.fixups.rebase_imported_paths) |prefix| {
1555 const slice = tree.tokenSlice(builtin_token);
1556 if (mem.eql(u8, slice, "@import")) f: {
1557 const param = params[0];
1558 const str_lit_token = tree.nodeMainToken(param);
1559 assert(tree.tokenTag(str_lit_token) == .string_literal);
1560 const token_bytes = tree.tokenSlice(str_lit_token);
1561 const imported_string = std.zig.string_literal.parseAlloc(r.gpa, token_bytes) catch |err| switch (err) {
1562 error.OutOfMemory => return error.OutOfMemory,
1563 error.InvalidLiteral => break :f,
1564 };
1565 defer r.gpa.free(imported_string);
1566 const new_string = try std.fs.path.resolvePosix(r.gpa, &.{ prefix, imported_string });
1567 defer r.gpa.free(new_string);
1568
1569 try renderToken(r, builtin_token + 1, .none); // (
1570 try ais.writer().print("\"{f}\"", .{std.zig.fmtString(new_string)});
1571 return renderToken(r, str_lit_token + 1, space); // )
1572 }
1573 }
1574
1575 const last_param = params[params.len - 1];
1576 const after_last_param_token = tree.lastToken(last_param) + 1;
1577
1578 if (tree.tokenTag(after_last_param_token) != .comma) {
1579 // Render all on one line, no trailing comma.
1580 try renderToken(r, builtin_token + 1, .none); // (
1581
1582 for (params, 0..) |param_node, i| {
1583 const first_param_token = tree.firstToken(param_node);
1584 if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or
1585 hasSameLineComment(tree, first_param_token - 1))
1586 {
1587 try ais.pushIndent(.normal);
1588 try renderExpression(r, param_node, .none);
1589 ais.popIndent();
1590 } else {
1591 try renderExpression(r, param_node, .none);
1592 }
1593
1594 if (i + 1 < params.len) {
1595 const comma_token = tree.lastToken(param_node) + 1;
1596 try renderToken(r, comma_token, .space); // ,
1597 }
1598 }
1599 return renderToken(r, after_last_param_token, space); // )
1600 } else {
1601 // Render one param per line.
1602 try ais.pushIndent(.normal);
1603 try renderToken(r, builtin_token + 1, Space.newline); // (
1604
1605 for (params) |param_node| {
1606 try ais.pushSpace(.comma);
1607 try renderExpression(r, param_node, .comma);
1608 ais.popSpace();
1609 }
1610 ais.popIndent();
1611
1612 return renderToken(r, after_last_param_token + 1, space); // )
1613 }
1614}
1615
1616fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void {
1617 const tree = r.tree;
1618 const ais = r.ais;
1619
1620 const after_fn_token = fn_proto.ast.fn_token + 1;
1621 const lparen = if (tree.tokenTag(after_fn_token) == .identifier) blk: {
1622 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1623 try renderIdentifier(r, after_fn_token, .none, .preserve_when_shadowing); // name
1624 break :blk after_fn_token + 1;
1625 } else blk: {
1626 try renderToken(r, fn_proto.ast.fn_token, .space); // fn
1627 break :blk fn_proto.ast.fn_token + 1;
1628 };
1629 assert(tree.tokenTag(lparen) == .l_paren);
1630
1631 const return_type = fn_proto.ast.return_type.unwrap().?;
1632 const maybe_bang = tree.firstToken(return_type) - 1;
1633 const rparen = blk: {
1634 // These may appear in any order, so we have to check the token_starts array
1635 // to find out which is first.
1636 var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang;
1637 var smallest_start = tree.tokenStart(maybe_bang);
1638 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1639 const tok = tree.firstToken(align_expr) - 3;
1640 const start = tree.tokenStart(tok);
1641 if (start < smallest_start) {
1642 rparen = tok;
1643 smallest_start = start;
1644 }
1645 }
1646 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1647 const tok = tree.firstToken(addrspace_expr) - 3;
1648 const start = tree.tokenStart(tok);
1649 if (start < smallest_start) {
1650 rparen = tok;
1651 smallest_start = start;
1652 }
1653 }
1654 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1655 const tok = tree.firstToken(section_expr) - 3;
1656 const start = tree.tokenStart(tok);
1657 if (start < smallest_start) {
1658 rparen = tok;
1659 smallest_start = start;
1660 }
1661 }
1662 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1663 const tok = tree.firstToken(callconv_expr) - 3;
1664 const start = tree.tokenStart(tok);
1665 if (start < smallest_start) {
1666 rparen = tok;
1667 smallest_start = start;
1668 }
1669 }
1670 break :blk rparen;
1671 };
1672 assert(tree.tokenTag(rparen) == .r_paren);
1673
1674 // The params list is a sparse set that does *not* include anytype or ... parameters.
1675
1676 const trailing_comma = tree.tokenTag(rparen - 1) == .comma;
1677 if (!trailing_comma and !hasComment(tree, lparen, rparen)) {
1678 // Render all on one line, no trailing comma.
1679 try renderToken(r, lparen, .none); // (
1680
1681 var param_i: usize = 0;
1682 var last_param_token = lparen;
1683 while (true) {
1684 last_param_token += 1;
1685 switch (tree.tokenTag(last_param_token)) {
1686 .doc_comment => {
1687 try renderToken(r, last_param_token, .newline);
1688 continue;
1689 },
1690 .ellipsis3 => {
1691 try renderToken(r, last_param_token, .none); // ...
1692 break;
1693 },
1694 .keyword_noalias, .keyword_comptime => {
1695 try renderToken(r, last_param_token, .space);
1696 last_param_token += 1;
1697 },
1698 .identifier => {},
1699 .keyword_anytype => {
1700 try renderToken(r, last_param_token, .none); // anytype
1701 continue;
1702 },
1703 .r_paren => break,
1704 .comma => {
1705 try renderToken(r, last_param_token, .space); // ,
1706 continue;
1707 },
1708 else => {}, // Parameter type without a name.
1709 }
1710 if (tree.tokenTag(last_param_token) == .identifier and
1711 tree.tokenTag(last_param_token + 1) == .colon)
1712 {
1713 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1714 last_param_token = last_param_token + 1;
1715 try renderToken(r, last_param_token, .space); // :
1716 last_param_token += 1;
1717 }
1718 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1719 try renderToken(r, last_param_token, .none); // anytype
1720 continue;
1721 }
1722 const param = fn_proto.ast.params[param_i];
1723 param_i += 1;
1724 try renderExpression(r, param, .none);
1725 last_param_token = tree.lastToken(param);
1726 }
1727 } else {
1728 // One param per line.
1729 try ais.pushIndent(.normal);
1730 try renderToken(r, lparen, .newline); // (
1731
1732 var param_i: usize = 0;
1733 var last_param_token = lparen;
1734 while (true) {
1735 last_param_token += 1;
1736 switch (tree.tokenTag(last_param_token)) {
1737 .doc_comment => {
1738 try renderToken(r, last_param_token, .newline);
1739 continue;
1740 },
1741 .ellipsis3 => {
1742 try renderToken(r, last_param_token, .comma); // ...
1743 break;
1744 },
1745 .keyword_noalias, .keyword_comptime => {
1746 try renderToken(r, last_param_token, .space);
1747 last_param_token += 1;
1748 },
1749 .identifier => {},
1750 .keyword_anytype => {
1751 try renderToken(r, last_param_token, .comma); // anytype
1752 if (tree.tokenTag(last_param_token + 1) == .comma)
1753 last_param_token += 1;
1754 continue;
1755 },
1756 .r_paren => break,
1757 else => {}, // Parameter type without a name.
1758 }
1759 if (tree.tokenTag(last_param_token) == .identifier and
1760 tree.tokenTag(last_param_token + 1) == .colon)
1761 {
1762 try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name
1763 last_param_token += 1;
1764 try renderToken(r, last_param_token, .space); // :
1765 last_param_token += 1;
1766 }
1767 if (tree.tokenTag(last_param_token) == .keyword_anytype) {
1768 try renderToken(r, last_param_token, .comma); // anytype
1769 if (tree.tokenTag(last_param_token + 1) == .comma)
1770 last_param_token += 1;
1771 continue;
1772 }
1773 const param = fn_proto.ast.params[param_i];
1774 param_i += 1;
1775 try ais.pushSpace(.comma);
1776 try renderExpression(r, param, .comma);
1777 ais.popSpace();
1778 last_param_token = tree.lastToken(param);
1779 if (tree.tokenTag(last_param_token + 1) == .comma) last_param_token += 1;
1780 }
1781 ais.popIndent();
1782 }
1783
1784 try renderToken(r, rparen, .space); // )
1785
1786 if (fn_proto.ast.align_expr.unwrap()) |align_expr| {
1787 const align_lparen = tree.firstToken(align_expr) - 1;
1788 const align_rparen = tree.lastToken(align_expr) + 1;
1789
1790 try renderToken(r, align_lparen - 1, .none); // align
1791 try renderToken(r, align_lparen, .none); // (
1792 try renderExpression(r, align_expr, .none);
1793 try renderToken(r, align_rparen, .space); // )
1794 }
1795
1796 if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| {
1797 const align_lparen = tree.firstToken(addrspace_expr) - 1;
1798 const align_rparen = tree.lastToken(addrspace_expr) + 1;
1799
1800 try renderToken(r, align_lparen - 1, .none); // addrspace
1801 try renderToken(r, align_lparen, .none); // (
1802 try renderExpression(r, addrspace_expr, .none);
1803 try renderToken(r, align_rparen, .space); // )
1804 }
1805
1806 if (fn_proto.ast.section_expr.unwrap()) |section_expr| {
1807 const section_lparen = tree.firstToken(section_expr) - 1;
1808 const section_rparen = tree.lastToken(section_expr) + 1;
1809
1810 try renderToken(r, section_lparen - 1, .none); // section
1811 try renderToken(r, section_lparen, .none); // (
1812 try renderExpression(r, section_expr, .none);
1813 try renderToken(r, section_rparen, .space); // )
1814 }
1815
1816 if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| {
1817 // Keep in sync with logic in `renderMember`. Search this file for the marker PROMOTE_CALLCONV_INLINE
1818 const is_callconv_inline = mem.eql(u8, "@\"inline\"", tree.tokenSlice(tree.nodeMainToken(callconv_expr)));
1819 const is_declaration = fn_proto.name_token != null;
1820 if (!(is_declaration and is_callconv_inline)) {
1821 const callconv_lparen = tree.firstToken(callconv_expr) - 1;
1822 const callconv_rparen = tree.lastToken(callconv_expr) + 1;
1823
1824 try renderToken(r, callconv_lparen - 1, .none); // callconv
1825 try renderToken(r, callconv_lparen, .none); // (
1826 try renderExpression(r, callconv_expr, .none);
1827 try renderToken(r, callconv_rparen, .space); // )
1828 }
1829 }
1830
1831 if (tree.tokenTag(maybe_bang) == .bang) {
1832 try renderToken(r, maybe_bang, .none); // !
1833 }
1834 return renderExpression(r, return_type, space);
1835}
1836
1837fn renderSwitchCase(
1838 r: *Render,
1839 switch_case: Ast.full.SwitchCase,
1840 space: Space,
1841) Error!void {
1842 const ais = r.ais;
1843 const tree = r.tree;
1844 const trailing_comma = tree.tokenTag(switch_case.ast.arrow_token - 1) == .comma;
1845 const has_comment_before_arrow = blk: {
1846 if (switch_case.ast.values.len == 0) break :blk false;
1847 break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token);
1848 };
1849
1850 // render inline keyword
1851 if (switch_case.inline_token) |some| {
1852 try renderToken(r, some, .space);
1853 }
1854
1855 // Render everything before the arrow
1856 if (switch_case.ast.values.len == 0) {
1857 try renderToken(r, switch_case.ast.arrow_token - 1, .space); // else keyword
1858 } else if (trailing_comma or has_comment_before_arrow) {
1859 // Render each value on a new line
1860 try ais.pushSpace(.comma);
1861 try renderExpressions(r, switch_case.ast.values, .comma);
1862 ais.popSpace();
1863 } else {
1864 // Render on one line
1865 for (switch_case.ast.values) |value_expr| {
1866 try renderExpression(r, value_expr, .comma_space);
1867 }
1868 }
1869
1870 // Render the arrow and everything after it
1871 const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal)
1872 // Newline gets inserted when rendering the target expr.
1873 Space.none
1874 else
1875 Space.space;
1876 const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space;
1877 try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // =>
1878
1879 if (switch_case.payload_token) |payload_token| {
1880 try renderToken(r, payload_token - 1, .none); // pipe
1881 const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk);
1882 if (tree.tokenTag(payload_token) == .asterisk) {
1883 try renderToken(r, payload_token, .none); // asterisk
1884 }
1885 try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier
1886 if (tree.tokenTag(ident + 1) == .comma) {
1887 try renderToken(r, ident + 1, .space); // ,
1888 try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier
1889 try renderToken(r, ident + 3, pre_target_space); // pipe
1890 } else {
1891 try renderToken(r, ident + 1, pre_target_space); // pipe
1892 }
1893 }
1894
1895 try renderExpression(r, switch_case.ast.target_expr, space);
1896}
1897
1898fn renderBlock(
1899 r: *Render,
1900 block_node: Ast.Node.Index,
1901 statements: []const Ast.Node.Index,
1902 space: Space,
1903) Error!void {
1904 const tree = r.tree;
1905 const ais = r.ais;
1906 const lbrace = tree.nodeMainToken(block_node);
1907
1908 if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
1909 try renderIdentifier(r, lbrace - 2, .none, .eagerly_unquote); // identifier
1910 try renderToken(r, lbrace - 1, .space); // :
1911 }
1912 try ais.pushIndent(.normal);
1913 if (statements.len == 0) {
1914 try renderToken(r, lbrace, .none);
1915 ais.popIndent();
1916 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1917 return;
1918 }
1919 try renderToken(r, lbrace, .newline);
1920 return finishRenderBlock(r, block_node, statements, space);
1921}
1922
1923fn finishRenderBlock(
1924 r: *Render,
1925 block_node: Ast.Node.Index,
1926 statements: []const Ast.Node.Index,
1927 space: Space,
1928) Error!void {
1929 const tree = r.tree;
1930 const ais = r.ais;
1931 for (statements, 0..) |stmt, i| {
1932 if (i != 0) try renderExtraNewline(r, stmt);
1933 if (r.fixups.omit_nodes.contains(stmt)) continue;
1934 try ais.pushSpace(.semicolon);
1935 switch (tree.nodeTag(stmt)) {
1936 .global_var_decl,
1937 .local_var_decl,
1938 .simple_var_decl,
1939 .aligned_var_decl,
1940 => try renderVarDecl(r, tree.fullVarDecl(stmt).?, false, .semicolon),
1941
1942 else => try renderExpression(r, stmt, .semicolon),
1943 }
1944 ais.popSpace();
1945 }
1946 ais.popIndent();
1947
1948 try renderToken(r, tree.lastToken(block_node), space); // rbrace
1949}
1950
1951fn renderStructInit(
1952 r: *Render,
1953 struct_node: Ast.Node.Index,
1954 struct_init: Ast.full.StructInit,
1955 space: Space,
1956) Error!void {
1957 const tree = r.tree;
1958 const ais = r.ais;
1959
1960 if (struct_init.ast.type_expr.unwrap()) |type_expr| {
1961 try renderExpression(r, type_expr, .none); // T
1962 } else {
1963 try renderToken(r, struct_init.ast.lbrace - 1, .none); // .
1964 }
1965
1966 if (struct_init.ast.fields.len == 0) {
1967 try ais.pushIndent(.normal);
1968 try renderToken(r, struct_init.ast.lbrace, .none); // lbrace
1969 ais.popIndent();
1970 return renderToken(r, struct_init.ast.lbrace + 1, space); // rbrace
1971 }
1972
1973 const rbrace = tree.lastToken(struct_node);
1974 const trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
1975 if (trailing_comma or hasComment(tree, struct_init.ast.lbrace, rbrace)) {
1976 // Render one field init per line.
1977 try ais.pushIndent(.normal);
1978 try renderToken(r, struct_init.ast.lbrace, .newline);
1979
1980 try renderToken(r, struct_init.ast.lbrace + 1, .none); // .
1981 try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name
1982 // Don't output a space after the = if expression is a multiline string,
1983 // since then it will start on the next line.
1984 const field_node = struct_init.ast.fields[0];
1985 const expr = tree.nodeTag(field_node);
1986 var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space;
1987 try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // =
1988
1989 try ais.pushSpace(.comma);
1990 try renderExpressionFixup(r, field_node, .comma);
1991 ais.popSpace();
1992
1993 for (struct_init.ast.fields[1..]) |field_init| {
1994 const init_token = tree.firstToken(field_init);
1995 try renderExtraNewlineToken(r, init_token - 3);
1996 try renderToken(r, init_token - 3, .none); // .
1997 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
1998 space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space;
1999 try renderToken(r, init_token - 1, space_after_equal); // =
2000
2001 try ais.pushSpace(.comma);
2002 try renderExpressionFixup(r, field_init, .comma);
2003 ais.popSpace();
2004 }
2005
2006 ais.popIndent();
2007 } else {
2008 // Render all on one line, no trailing comma.
2009 try renderToken(r, struct_init.ast.lbrace, .space);
2010
2011 for (struct_init.ast.fields) |field_init| {
2012 const init_token = tree.firstToken(field_init);
2013 try renderToken(r, init_token - 3, .none); // .
2014 try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name
2015 try renderToken(r, init_token - 1, .space); // =
2016 try renderExpressionFixup(r, field_init, .comma_space);
2017 }
2018 }
2019
2020 return renderToken(r, rbrace, space);
2021}
2022
2023fn renderArrayInit(
2024 r: *Render,
2025 array_init: Ast.full.ArrayInit,
2026 space: Space,
2027) Error!void {
2028 const tree = r.tree;
2029 const ais = r.ais;
2030 const gpa = r.gpa;
2031
2032 if (array_init.ast.type_expr.unwrap()) |type_expr| {
2033 try renderExpression(r, type_expr, .none); // T
2034 } else {
2035 try renderToken(r, array_init.ast.lbrace - 1, .none); // .
2036 }
2037
2038 if (array_init.ast.elements.len == 0) {
2039 try ais.pushIndent(.normal);
2040 try renderToken(r, array_init.ast.lbrace, .none); // lbrace
2041 ais.popIndent();
2042 return renderToken(r, array_init.ast.lbrace + 1, space); // rbrace
2043 }
2044
2045 const last_elem = array_init.ast.elements[array_init.ast.elements.len - 1];
2046 const last_elem_token = tree.lastToken(last_elem);
2047 const trailing_comma = tree.tokenTag(last_elem_token + 1) == .comma;
2048 const rbrace = if (trailing_comma) last_elem_token + 2 else last_elem_token + 1;
2049 assert(tree.tokenTag(rbrace) == .r_brace);
2050
2051 if (array_init.ast.elements.len == 1) {
2052 const only_elem = array_init.ast.elements[0];
2053 const first_token = tree.firstToken(only_elem);
2054 if (tree.tokenTag(first_token) != .multiline_string_literal_line and
2055 !anythingBetween(tree, last_elem_token, rbrace))
2056 {
2057 try renderToken(r, array_init.ast.lbrace, .none);
2058 try renderExpression(r, only_elem, .none);
2059 return renderToken(r, rbrace, space);
2060 }
2061 }
2062
2063 const contains_comment = hasComment(tree, array_init.ast.lbrace, rbrace);
2064 const contains_multiline_string = hasMultilineString(tree, array_init.ast.lbrace, rbrace);
2065
2066 if (!trailing_comma and !contains_comment and !contains_multiline_string) {
2067 // Render all on one line, no trailing comma.
2068 if (array_init.ast.elements.len == 1) {
2069 // If there is only one element, we don't use spaces
2070 try renderToken(r, array_init.ast.lbrace, .none);
2071 try renderExpression(r, array_init.ast.elements[0], .none);
2072 } else {
2073 try renderToken(r, array_init.ast.lbrace, .space);
2074 for (array_init.ast.elements) |elem| {
2075 try renderExpression(r, elem, .comma_space);
2076 }
2077 }
2078 return renderToken(r, last_elem_token + 1, space); // rbrace
2079 }
2080
2081 try ais.pushIndent(.normal);
2082 try renderToken(r, array_init.ast.lbrace, .newline);
2083
2084 var expr_index: usize = 0;
2085 while (true) {
2086 const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2087 const row_exprs = array_init.ast.elements[expr_index..];
2088 // A place to store the width of each expression and its column's maximum
2089 const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2090 defer gpa.free(widths);
2091 @memset(widths, 0);
2092
2093 const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2094 defer gpa.free(expr_newlines);
2095 @memset(expr_newlines, false);
2096
2097 const expr_widths = widths[0..row_exprs.len];
2098 const column_widths = widths[row_exprs.len..];
2099
2100 // Find next row with trailing comment (if any) to end the current section.
2101 const section_end = sec_end: {
2102 var this_line_first_expr: usize = 0;
2103 var this_line_size = rowSize(tree, row_exprs, rbrace);
2104 for (row_exprs, 0..) |expr, i| {
2105 // Ignore comment on first line of this section.
2106 if (i == 0) continue;
2107 const expr_last_token = tree.lastToken(expr);
2108 if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2109 continue;
2110 // Track start of line containing comment.
2111 if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2112 this_line_first_expr = i;
2113 this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2114 }
2115
2116 const maybe_comma = expr_last_token + 1;
2117 if (tree.tokenTag(maybe_comma) == .comma) {
2118 if (hasSameLineComment(tree, maybe_comma))
2119 break :sec_end i - this_line_size + 1;
2120 }
2121 }
2122 break :sec_end row_exprs.len;
2123 };
2124 expr_index += section_end;
2125
2126 const section_exprs = row_exprs[0..section_end];
2127
2128 var sub_expr_buffer = std.ArrayList(u8).init(gpa);
2129 defer sub_expr_buffer.deinit();
2130
2131 const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2132 defer gpa.free(sub_expr_buffer_starts);
2133
2134 var auto_indenting_stream = Ais.init(&sub_expr_buffer, indent_delta);
2135 defer auto_indenting_stream.deinit();
2136 var sub_render: Render = .{
2137 .gpa = r.gpa,
2138 .ais = &auto_indenting_stream,
2139 .tree = r.tree,
2140 .fixups = r.fixups,
2141 };
2142
2143 // Calculate size of columns in current section
2144 var column_counter: usize = 0;
2145 var single_line = true;
2146 var contains_newline = false;
2147 for (section_exprs, 0..) |expr, i| {
2148 const start = sub_expr_buffer.items.len;
2149 sub_expr_buffer_starts[i] = start;
2150
2151 if (i + 1 < section_exprs.len) {
2152 try renderExpression(&sub_render, expr, .none);
2153 const width = sub_expr_buffer.items.len - start;
2154 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start..], '\n') != null;
2155 contains_newline = contains_newline or this_contains_newline;
2156 expr_widths[i] = width;
2157 expr_newlines[i] = this_contains_newline;
2158
2159 if (!this_contains_newline) {
2160 const column = column_counter % row_size;
2161 column_widths[column] = @max(column_widths[column], width);
2162
2163 const expr_last_token = tree.lastToken(expr) + 1;
2164 const next_expr = section_exprs[i + 1];
2165 column_counter += 1;
2166 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2167 } else {
2168 single_line = false;
2169 column_counter = 0;
2170 }
2171 } else {
2172 try ais.pushSpace(.comma);
2173 try renderExpression(&sub_render, expr, .comma);
2174 ais.popSpace();
2175
2176 const width = sub_expr_buffer.items.len - start - 2;
2177 const this_contains_newline = mem.indexOfScalar(u8, sub_expr_buffer.items[start .. sub_expr_buffer.items.len - 1], '\n') != null;
2178 contains_newline = contains_newline or this_contains_newline;
2179 expr_widths[i] = width;
2180 expr_newlines[i] = contains_newline;
2181
2182 if (!contains_newline) {
2183 const column = column_counter % row_size;
2184 column_widths[column] = @max(column_widths[column], width);
2185 }
2186 }
2187 }
2188 sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.items.len;
2189
2190 // Render exprs in current section.
2191 column_counter = 0;
2192 for (section_exprs, 0..) |expr, i| {
2193 const start = sub_expr_buffer_starts[i];
2194 const end = sub_expr_buffer_starts[i + 1];
2195 const expr_text = sub_expr_buffer.items[start..end];
2196 if (!expr_newlines[i]) {
2197 try ais.writer().writeAll(expr_text);
2198 } else {
2199 var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2200 var last_line_was_empty = false;
2201 try ais.writer().writeAll(by_line.first());
2202 while (by_line.next()) |line| {
2203 if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2204 try ais.insertNewline();
2205 } else {
2206 try ais.maybeInsertNewline();
2207 }
2208 last_line_was_empty = (line.len == 0);
2209 try ais.writer().writeAll(line);
2210 }
2211 }
2212
2213 if (i + 1 < section_exprs.len) {
2214 const next_expr = section_exprs[i + 1];
2215 const comma = tree.lastToken(expr) + 1;
2216
2217 if (column_counter != row_size - 1) {
2218 if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2219 // Neither the current or next expression is multiline
2220 try renderToken(r, comma, .space); // ,
2221 assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2222 const padding = column_widths[column_counter % row_size] - expr_widths[i];
2223 try ais.writer().writeByteNTimes(' ', padding);
2224
2225 column_counter += 1;
2226 continue;
2227 }
2228 }
2229
2230 if (single_line and row_size != 1) {
2231 try renderToken(r, comma, .space); // ,
2232 continue;
2233 }
2234
2235 column_counter = 0;
2236 try renderToken(r, comma, .newline); // ,
2237 try renderExtraNewline(r, next_expr);
2238 }
2239 }
2240
2241 if (expr_index == array_init.ast.elements.len)
2242 break;
2243 }
2244
2245 ais.popIndent();
2246 return renderToken(r, rbrace, space); // rbrace
2247}
2248
2249fn renderContainerDecl(
2250 r: *Render,
2251 container_decl_node: Ast.Node.Index,
2252 container_decl: Ast.full.ContainerDecl,
2253 space: Space,
2254) Error!void {
2255 const tree = r.tree;
2256 const ais = r.ais;
2257
2258 if (container_decl.layout_token) |layout_token| {
2259 try renderToken(r, layout_token, .space);
2260 }
2261
2262 const container: Container = switch (tree.tokenTag(container_decl.ast.main_token)) {
2263 .keyword_enum => .@"enum",
2264 .keyword_struct => for (container_decl.ast.members) |member| {
2265 if (tree.fullContainerField(member)) |field| if (!field.ast.tuple_like) break .other;
2266 } else .tuple,
2267 else => .other,
2268 };
2269
2270 var lbrace: Ast.TokenIndex = undefined;
2271 if (container_decl.ast.enum_token) |enum_token| {
2272 try renderToken(r, container_decl.ast.main_token, .none); // union
2273 try renderToken(r, enum_token - 1, .none); // lparen
2274 try renderToken(r, enum_token, .none); // enum
2275 if (container_decl.ast.arg.unwrap()) |arg| {
2276 try renderToken(r, enum_token + 1, .none); // lparen
2277 try renderExpression(r, arg, .none);
2278 const rparen = tree.lastToken(arg) + 1;
2279 try renderToken(r, rparen, .none); // rparen
2280 try renderToken(r, rparen + 1, .space); // rparen
2281 lbrace = rparen + 2;
2282 } else {
2283 try renderToken(r, enum_token + 1, .space); // rparen
2284 lbrace = enum_token + 2;
2285 }
2286 } else if (container_decl.ast.arg.unwrap()) |arg| {
2287 try renderToken(r, container_decl.ast.main_token, .none); // union
2288 try renderToken(r, container_decl.ast.main_token + 1, .none); // lparen
2289 try renderExpression(r, arg, .none);
2290 const rparen = tree.lastToken(arg) + 1;
2291 try renderToken(r, rparen, .space); // rparen
2292 lbrace = rparen + 1;
2293 } else {
2294 try renderToken(r, container_decl.ast.main_token, .space); // union
2295 lbrace = container_decl.ast.main_token + 1;
2296 }
2297
2298 const rbrace = tree.lastToken(container_decl_node);
2299
2300 if (container_decl.ast.members.len == 0) {
2301 try ais.pushIndent(.normal);
2302 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2303 try renderToken(r, lbrace, .newline); // lbrace
2304 try renderContainerDocComments(r, lbrace + 1);
2305 } else {
2306 try renderToken(r, lbrace, .none); // lbrace
2307 }
2308 ais.popIndent();
2309 return renderToken(r, rbrace, space); // rbrace
2310 }
2311
2312 const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma;
2313 if (!src_has_trailing_comma) one_line: {
2314 // We print all the members in-line unless one of the following conditions are true:
2315
2316 // 1. The container has comments or multiline strings.
2317 if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
2318 break :one_line;
2319 }
2320
2321 // 2. The container has a container comment.
2322 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line;
2323
2324 // 3. A member of the container has a doc comment.
2325 for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| {
2326 if (tag == .doc_comment) break :one_line;
2327 }
2328
2329 // 4. The container has non-field members.
2330 for (container_decl.ast.members) |member| {
2331 if (tree.fullContainerField(member) == null) break :one_line;
2332 }
2333
2334 // Print all the declarations on the same line.
2335 try renderToken(r, lbrace, .space); // lbrace
2336 for (container_decl.ast.members) |member| {
2337 try renderMember(r, container, member, .space);
2338 }
2339 return renderToken(r, rbrace, space); // rbrace
2340 }
2341
2342 // One member per line.
2343 try ais.pushIndent(.normal);
2344 try renderToken(r, lbrace, .newline); // lbrace
2345 if (tree.tokenTag(lbrace + 1) == .container_doc_comment) {
2346 try renderContainerDocComments(r, lbrace + 1);
2347 }
2348 for (container_decl.ast.members, 0..) |member, i| {
2349 if (i != 0) try renderExtraNewline(r, member);
2350 switch (tree.nodeTag(member)) {
2351 // For container fields, ensure a trailing comma is added if necessary.
2352 .container_field_init,
2353 .container_field_align,
2354 .container_field,
2355 => {
2356 try ais.pushSpace(.comma);
2357 try renderMember(r, container, member, .comma);
2358 ais.popSpace();
2359 },
2360
2361 else => try renderMember(r, container, member, .newline),
2362 }
2363 }
2364 ais.popIndent();
2365
2366 return renderToken(r, rbrace, space); // rbrace
2367}
2368
2369fn renderAsmLegacy(
2370 r: *Render,
2371 asm_node: Ast.full.AsmLegacy,
2372 space: Space,
2373) Error!void {
2374 const tree = r.tree;
2375 const ais = r.ais;
2376
2377 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2378
2379 if (asm_node.volatile_token) |volatile_token| {
2380 try renderToken(r, volatile_token, .space); // volatile
2381 try renderToken(r, volatile_token + 1, .none); // lparen
2382 } else {
2383 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2384 }
2385
2386 if (asm_node.ast.items.len == 0) {
2387 try ais.forcePushIndent(.normal);
2388 if (asm_node.first_clobber) |first_clobber| {
2389 // asm ("foo" ::: "a", "b")
2390 // asm ("foo" ::: "a", "b",)
2391 try renderExpression(r, asm_node.ast.template, .space);
2392 // Render the three colons.
2393 try renderToken(r, first_clobber - 3, .none);
2394 try renderToken(r, first_clobber - 2, .none);
2395 try renderToken(r, first_clobber - 1, .space);
2396
2397 try ais.writer().writeAll(".{ ");
2398
2399 var tok_i = first_clobber;
2400 while (true) : (tok_i += 1) {
2401 try ais.writer().writeByte('.');
2402 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2403 try ais.writer().writeAll(" = true");
2404
2405 tok_i += 1;
2406 switch (tree.tokenTag(tok_i)) {
2407 .r_paren => {
2408 try ais.writer().writeAll(" }");
2409 ais.popIndent();
2410 return renderToken(r, tok_i, space);
2411 },
2412 .comma => {
2413 if (tree.tokenTag(tok_i + 1) == .r_paren) {
2414 try ais.writer().writeAll(" }");
2415 ais.popIndent();
2416 return renderToken(r, tok_i + 1, space);
2417 } else {
2418 try renderToken(r, tok_i, .space);
2419 }
2420 },
2421 else => unreachable,
2422 }
2423 }
2424 } else {
2425 unreachable;
2426 }
2427 }
2428
2429 try ais.forcePushIndent(.normal);
2430 try renderExpression(r, asm_node.ast.template, .newline);
2431 ais.setIndentDelta(asm_indent_delta);
2432 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2433
2434 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2435 try renderToken(r, colon1, .newline); // :
2436 break :colon2 colon1 + 1;
2437 } else colon2: {
2438 try renderToken(r, colon1, .space); // :
2439
2440 try ais.forcePushIndent(.normal);
2441 for (asm_node.outputs, 0..) |asm_output, i| {
2442 if (i + 1 < asm_node.outputs.len) {
2443 const next_asm_output = asm_node.outputs[i + 1];
2444 try renderAsmOutput(r, asm_output, .none);
2445
2446 const comma = tree.firstToken(next_asm_output) - 1;
2447 try renderToken(r, comma, .newline); // ,
2448 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2449 } else if (asm_node.inputs.len == 0 and asm_node.first_clobber == null) {
2450 try ais.pushSpace(.comma);
2451 try renderAsmOutput(r, asm_output, .comma);
2452 ais.popSpace();
2453 ais.popIndent();
2454 ais.setIndentDelta(indent_delta);
2455 ais.popIndent();
2456 return renderToken(r, asm_node.ast.rparen, space); // rparen
2457 } else {
2458 try ais.pushSpace(.comma);
2459 try renderAsmOutput(r, asm_output, .comma);
2460 ais.popSpace();
2461 const comma_or_colon = tree.lastToken(asm_output) + 1;
2462 ais.popIndent();
2463 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2464 .comma => comma_or_colon + 1,
2465 else => comma_or_colon,
2466 };
2467 }
2468 } else unreachable;
2469 };
2470
2471 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2472 try renderToken(r, colon2, .newline); // :
2473 break :colon3 colon2 + 1;
2474 } else colon3: {
2475 try renderToken(r, colon2, .space); // :
2476 try ais.forcePushIndent(.normal);
2477 for (asm_node.inputs, 0..) |asm_input, i| {
2478 if (i + 1 < asm_node.inputs.len) {
2479 const next_asm_input = asm_node.inputs[i + 1];
2480 try renderAsmInput(r, asm_input, .none);
2481
2482 const first_token = tree.firstToken(next_asm_input);
2483 try renderToken(r, first_token - 1, .newline); // ,
2484 try renderExtraNewlineToken(r, first_token);
2485 } else if (asm_node.first_clobber == null) {
2486 try ais.pushSpace(.comma);
2487 try renderAsmInput(r, asm_input, .comma);
2488 ais.popSpace();
2489 ais.popIndent();
2490 ais.setIndentDelta(indent_delta);
2491 ais.popIndent();
2492 return renderToken(r, asm_node.ast.rparen, space); // rparen
2493 } else {
2494 try ais.pushSpace(.comma);
2495 try renderAsmInput(r, asm_input, .comma);
2496 ais.popSpace();
2497 const comma_or_colon = tree.lastToken(asm_input) + 1;
2498 ais.popIndent();
2499 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2500 .comma => comma_or_colon + 1,
2501 else => comma_or_colon,
2502 };
2503 }
2504 }
2505 unreachable;
2506 };
2507
2508 try renderToken(r, colon3, .space); // :
2509 try ais.writer().writeAll(".{ ");
2510 const first_clobber = asm_node.first_clobber.?;
2511 var tok_i = first_clobber;
2512 while (true) {
2513 switch (tree.tokenTag(tok_i + 1)) {
2514 .r_paren => {
2515 ais.setIndentDelta(indent_delta);
2516 try ais.writer().writeByte('.');
2517 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2518 try ais.writer().writeAll(" = true }");
2519 try renderSpace(r, tok_i, lexeme_len, .newline);
2520 ais.popIndent();
2521 return renderToken(r, tok_i + 1, space);
2522 },
2523 .comma => {
2524 switch (tree.tokenTag(tok_i + 2)) {
2525 .r_paren => {
2526 ais.setIndentDelta(indent_delta);
2527 try ais.writer().writeByte('.');
2528 const lexeme_len = try writeStringLiteralAsIdentifier(r, tok_i);
2529 try ais.writer().writeAll(" = true }");
2530 try renderSpace(r, tok_i, lexeme_len, .newline);
2531 ais.popIndent();
2532 return renderToken(r, tok_i + 2, space);
2533 },
2534 else => {
2535 try ais.writer().writeByte('.');
2536 _ = try writeStringLiteralAsIdentifier(r, tok_i);
2537 try ais.writer().writeAll(" = true");
2538 try renderToken(r, tok_i + 1, .space);
2539 tok_i += 2;
2540 },
2541 }
2542 },
2543 else => unreachable,
2544 }
2545 }
2546}
2547
2548fn renderAsm(
2549 r: *Render,
2550 asm_node: Ast.full.Asm,
2551 space: Space,
2552) Error!void {
2553 const tree = r.tree;
2554 const ais = r.ais;
2555
2556 try renderToken(r, asm_node.ast.asm_token, .space); // asm
2557
2558 if (asm_node.volatile_token) |volatile_token| {
2559 try renderToken(r, volatile_token, .space); // volatile
2560 try renderToken(r, volatile_token + 1, .none); // lparen
2561 } else {
2562 try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
2563 }
2564
2565 if (asm_node.ast.items.len == 0) {
2566 try ais.forcePushIndent(.normal);
2567 if (asm_node.ast.clobbers.unwrap()) |clobbers| {
2568 // asm ("foo" ::: clobbers)
2569 try renderExpression(r, asm_node.ast.template, .space);
2570 // Render the three colons.
2571 const first_clobber = tree.firstToken(clobbers);
2572 try renderToken(r, first_clobber - 3, .none);
2573 try renderToken(r, first_clobber - 2, .none);
2574 try renderToken(r, first_clobber - 1, .space);
2575 try renderExpression(r, clobbers, .none);
2576 ais.popIndent();
2577 return renderToken(r, asm_node.ast.rparen, space); // rparen
2578 }
2579
2580 // asm ("foo")
2581 try renderExpression(r, asm_node.ast.template, .none);
2582 ais.popIndent();
2583 return renderToken(r, asm_node.ast.rparen, space); // rparen
2584 }
2585
2586 try ais.forcePushIndent(.normal);
2587 try renderExpression(r, asm_node.ast.template, .newline);
2588 ais.setIndentDelta(asm_indent_delta);
2589 const colon1 = tree.lastToken(asm_node.ast.template) + 1;
2590
2591 const colon2 = if (asm_node.outputs.len == 0) colon2: {
2592 try renderToken(r, colon1, .newline); // :
2593 break :colon2 colon1 + 1;
2594 } else colon2: {
2595 try renderToken(r, colon1, .space); // :
2596
2597 try ais.forcePushIndent(.normal);
2598 for (asm_node.outputs, 0..) |asm_output, i| {
2599 if (i + 1 < asm_node.outputs.len) {
2600 const next_asm_output = asm_node.outputs[i + 1];
2601 try renderAsmOutput(r, asm_output, .none);
2602
2603 const comma = tree.firstToken(next_asm_output) - 1;
2604 try renderToken(r, comma, .newline); // ,
2605 try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
2606 } else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
2607 try ais.pushSpace(.comma);
2608 try renderAsmOutput(r, asm_output, .comma);
2609 ais.popSpace();
2610 ais.popIndent();
2611 ais.setIndentDelta(indent_delta);
2612 ais.popIndent();
2613 return renderToken(r, asm_node.ast.rparen, space); // rparen
2614 } else {
2615 try ais.pushSpace(.comma);
2616 try renderAsmOutput(r, asm_output, .comma);
2617 ais.popSpace();
2618 const comma_or_colon = tree.lastToken(asm_output) + 1;
2619 ais.popIndent();
2620 break :colon2 switch (tree.tokenTag(comma_or_colon)) {
2621 .comma => comma_or_colon + 1,
2622 else => comma_or_colon,
2623 };
2624 }
2625 } else unreachable;
2626 };
2627
2628 const colon3 = if (asm_node.inputs.len == 0) colon3: {
2629 try renderToken(r, colon2, .newline); // :
2630 break :colon3 colon2 + 1;
2631 } else colon3: {
2632 try renderToken(r, colon2, .space); // :
2633 try ais.forcePushIndent(.normal);
2634 for (asm_node.inputs, 0..) |asm_input, i| {
2635 if (i + 1 < asm_node.inputs.len) {
2636 const next_asm_input = asm_node.inputs[i + 1];
2637 try renderAsmInput(r, asm_input, .none);
2638
2639 const first_token = tree.firstToken(next_asm_input);
2640 try renderToken(r, first_token - 1, .newline); // ,
2641 try renderExtraNewlineToken(r, first_token);
2642 } else if (asm_node.ast.clobbers == .none) {
2643 try ais.pushSpace(.comma);
2644 try renderAsmInput(r, asm_input, .comma);
2645 ais.popSpace();
2646 ais.popIndent();
2647 ais.setIndentDelta(indent_delta);
2648 ais.popIndent();
2649 return renderToken(r, asm_node.ast.rparen, space); // rparen
2650 } else {
2651 try ais.pushSpace(.comma);
2652 try renderAsmInput(r, asm_input, .comma);
2653 ais.popSpace();
2654 const comma_or_colon = tree.lastToken(asm_input) + 1;
2655 ais.popIndent();
2656 break :colon3 switch (tree.tokenTag(comma_or_colon)) {
2657 .comma => comma_or_colon + 1,
2658 else => comma_or_colon,
2659 };
2660 }
2661 }
2662 unreachable;
2663 };
2664
2665 try renderToken(r, colon3, .space); // :
2666 const clobbers = asm_node.ast.clobbers.unwrap().?;
2667 try renderExpression(r, clobbers, .none);
2668 ais.setIndentDelta(indent_delta);
2669 ais.popIndent();
2670 return renderToken(r, asm_node.ast.rparen, space); // rparen
2671}
2672
2673fn renderCall(
2674 r: *Render,
2675 call: Ast.full.Call,
2676 space: Space,
2677) Error!void {
2678 try renderExpression(r, call.ast.fn_expr, .none);
2679 try renderParamList(r, call.ast.lparen, call.ast.params, space);
2680}
2681
2682fn renderParamList(
2683 r: *Render,
2684 lparen: Ast.TokenIndex,
2685 params: []const Ast.Node.Index,
2686 space: Space,
2687) Error!void {
2688 const tree = r.tree;
2689 const ais = r.ais;
2690
2691 if (params.len == 0) {
2692 try ais.pushIndent(.normal);
2693 try renderToken(r, lparen, .none);
2694 ais.popIndent();
2695 return renderToken(r, lparen + 1, space); // )
2696 }
2697
2698 const last_param = params[params.len - 1];
2699 const after_last_param_tok = tree.lastToken(last_param) + 1;
2700 if (tree.tokenTag(after_last_param_tok) == .comma) {
2701 try ais.pushIndent(.normal);
2702 try renderToken(r, lparen, .newline); // (
2703 for (params, 0..) |param_node, i| {
2704 if (i + 1 < params.len) {
2705 try renderExpression(r, param_node, .none);
2706
2707 const comma = tree.lastToken(param_node) + 1;
2708 try renderToken(r, comma, .newline); // ,
2709
2710 try renderExtraNewline(r, params[i + 1]);
2711 } else {
2712 try ais.pushSpace(.comma);
2713 try renderExpression(r, param_node, .comma);
2714 ais.popSpace();
2715 }
2716 }
2717 ais.popIndent();
2718 return renderToken(r, after_last_param_tok + 1, space); // )
2719 }
2720
2721 try ais.pushIndent(.normal);
2722 try renderToken(r, lparen, .none); // (
2723 for (params, 0..) |param_node, i| {
2724 try renderExpression(r, param_node, .none);
2725
2726 if (i + 1 < params.len) {
2727 const comma = tree.lastToken(param_node) + 1;
2728 const next_multiline_string =
2729 tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line;
2730 const comma_space: Space = if (next_multiline_string) .none else .space;
2731 try renderToken(r, comma, comma_space);
2732 }
2733 }
2734 ais.popIndent();
2735 return renderToken(r, after_last_param_tok, space); // )
2736}
2737
2738/// Render an expression, and the comma that follows it, if it is present in the source.
2739/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2740fn renderExpressionComma(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
2741 const tree = r.tree;
2742 const maybe_comma = tree.lastToken(node) + 1;
2743 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2744 try renderExpression(r, node, .none);
2745 return renderToken(r, maybe_comma, space);
2746 } else {
2747 return renderExpression(r, node, space);
2748 }
2749}
2750
2751/// Render a token, and the comma that follows it, if it is present in the source.
2752/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2753fn renderTokenComma(r: *Render, token: Ast.TokenIndex, space: Space) Error!void {
2754 const tree = r.tree;
2755 const maybe_comma = token + 1;
2756 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2757 try renderToken(r, token, .none);
2758 return renderToken(r, maybe_comma, space);
2759 } else {
2760 return renderToken(r, token, space);
2761 }
2762}
2763
2764/// Render an identifier, and the comma that follows it, if it is present in the source.
2765/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
2766fn renderIdentifierComma(r: *Render, token: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2767 const tree = r.tree;
2768 const maybe_comma = token + 1;
2769 if (tree.tokenTag(maybe_comma) == .comma and space != .comma) {
2770 try renderIdentifier(r, token, .none, quote);
2771 return renderToken(r, maybe_comma, space);
2772 } else {
2773 return renderIdentifier(r, token, space, quote);
2774 }
2775}
2776
2777const Space = enum {
2778 /// Output the token lexeme only.
2779 none,
2780 /// Output the token lexeme followed by a single space.
2781 space,
2782 /// Output the token lexeme followed by a newline.
2783 newline,
2784 /// If the next token is a comma, render it as well. If not, insert one.
2785 /// In either case, a newline will be inserted afterwards.
2786 comma,
2787 /// Additionally consume the next token if it is a comma.
2788 /// In either case, a space will be inserted afterwards.
2789 comma_space,
2790 /// Additionally consume the next token if it is a semicolon.
2791 /// In either case, a newline will be inserted afterwards.
2792 semicolon,
2793 /// Skip rendering whitespace and comments. If this is used, the caller
2794 /// *must* handle whitespace and comments manually.
2795 skip,
2796};
2797
2798fn renderToken(r: *Render, token_index: Ast.TokenIndex, space: Space) Error!void {
2799 const tree = r.tree;
2800 const ais = r.ais;
2801 const lexeme = tokenSliceForRender(tree, token_index);
2802 try ais.writer().writeAll(lexeme);
2803 try renderSpace(r, token_index, lexeme.len, space);
2804}
2805
2806fn renderTokenOverrideSpaceMode(r: *Render, token_index: Ast.TokenIndex, space: Space, override_space: Space) Error!void {
2807 const tree = r.tree;
2808 const ais = r.ais;
2809 const lexeme = tokenSliceForRender(tree, token_index);
2810 try ais.writer().writeAll(lexeme);
2811 ais.enableSpaceMode(override_space);
2812 defer ais.disableSpaceMode();
2813 try renderSpace(r, token_index, lexeme.len, space);
2814}
2815
2816fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space: Space) Error!void {
2817 const tree = r.tree;
2818 const ais = r.ais;
2819
2820 const next_token_tag = tree.tokenTag(token_index + 1);
2821
2822 if (space == .skip) return;
2823
2824 if (space == .comma and next_token_tag != .comma) {
2825 try ais.writer().writeByte(',');
2826 }
2827 if (space == .semicolon or space == .comma) ais.enableSpaceMode(space);
2828 defer ais.disableSpaceMode();
2829 const comment = try renderComments(
2830 r,
2831 tree.tokenStart(token_index) + lexeme_len,
2832 tree.tokenStart(token_index + 1),
2833 );
2834 switch (space) {
2835 .none => {},
2836 .space => if (!comment) try ais.writer().writeByte(' '),
2837 .newline => if (!comment) try ais.insertNewline(),
2838
2839 .comma => if (next_token_tag == .comma) {
2840 try renderToken(r, token_index + 1, .newline);
2841 } else if (!comment) {
2842 try ais.insertNewline();
2843 },
2844
2845 .comma_space => if (next_token_tag == .comma) {
2846 try renderToken(r, token_index + 1, .space);
2847 } else if (!comment) {
2848 try ais.writer().writeByte(' ');
2849 },
2850
2851 .semicolon => if (next_token_tag == .semicolon) {
2852 try renderToken(r, token_index + 1, .newline);
2853 } else if (!comment) {
2854 try ais.insertNewline();
2855 },
2856
2857 .skip => unreachable,
2858 }
2859}
2860
2861fn renderOnlySpace(r: *Render, space: Space) Error!void {
2862 const ais = r.ais;
2863 switch (space) {
2864 .none => {},
2865 .space => try ais.writer().writeByte(' '),
2866 .newline => try ais.insertNewline(),
2867 .comma => try ais.writer().writeAll(",\n"),
2868 .comma_space => try ais.writer().writeAll(", "),
2869 .semicolon => try ais.writer().writeAll(";\n"),
2870 .skip => unreachable,
2871 }
2872}
2873
2874const QuoteBehavior = enum {
2875 preserve_when_shadowing,
2876 eagerly_unquote,
2877 eagerly_unquote_except_underscore,
2878};
2879
2880fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote: QuoteBehavior) Error!void {
2881 const tree = r.tree;
2882 assert(tree.tokenTag(token_index) == .identifier);
2883 const lexeme = tokenSliceForRender(tree, token_index);
2884
2885 if (r.fixups.rename_identifiers.get(lexeme)) |mangled| {
2886 try r.ais.writer().writeAll(mangled);
2887 try renderSpace(r, token_index, lexeme.len, space);
2888 return;
2889 }
2890
2891 if (lexeme[0] != '@') {
2892 return renderToken(r, token_index, space);
2893 }
2894
2895 assert(lexeme.len >= 3);
2896 assert(lexeme[0] == '@');
2897 assert(lexeme[1] == '\"');
2898 assert(lexeme[lexeme.len - 1] == '\"');
2899 const contents = lexeme[2 .. lexeme.len - 1]; // inside the @"" quotation
2900
2901 // Empty name can't be unquoted.
2902 if (contents.len == 0) {
2903 return renderQuotedIdentifier(r, token_index, space, false);
2904 }
2905
2906 // Special case for _.
2907 if (std.zig.isUnderscore(contents)) switch (quote) {
2908 .eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
2909 .eagerly_unquote_except_underscore,
2910 .preserve_when_shadowing,
2911 => return renderQuotedIdentifier(r, token_index, space, false),
2912 };
2913
2914 // Scan the entire name for characters that would (after un-escaping) be illegal in a symbol,
2915 // i.e. contents don't match: [A-Za-z_][A-Za-z0-9_]*
2916 var contents_i: usize = 0;
2917 while (contents_i < contents.len) {
2918 switch (contents[contents_i]) {
2919 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2920 'A'...'Z', 'a'...'z', '_' => {},
2921 '\\' => {
2922 var esc_offset = contents_i;
2923 const res = std.zig.string_literal.parseEscapeSequence(contents, &esc_offset);
2924 switch (res) {
2925 .success => |char| switch (char) {
2926 '0'...'9' => if (contents_i == 0) return renderQuotedIdentifier(r, token_index, space, false),
2927 'A'...'Z', 'a'...'z', '_' => {},
2928 else => return renderQuotedIdentifier(r, token_index, space, false),
2929 },
2930 .failure => return renderQuotedIdentifier(r, token_index, space, false),
2931 }
2932 contents_i += esc_offset;
2933 continue;
2934 },
2935 else => return renderQuotedIdentifier(r, token_index, space, false),
2936 }
2937 contents_i += 1;
2938 }
2939
2940 // Read enough of the name (while un-escaping) to determine if it's a keyword or primitive.
2941 // If it's too long to fit in this buffer, we know it's neither and quoting is unnecessary.
2942 // If we read the whole thing, we have to do further checks.
2943 const longest_keyword_or_primitive_len = comptime blk: {
2944 var longest = 0;
2945 for (primitives.names.keys()) |key| {
2946 if (key.len > longest) longest = key.len;
2947 }
2948 for (std.zig.Token.keywords.keys()) |key| {
2949 if (key.len > longest) longest = key.len;
2950 }
2951 break :blk longest;
2952 };
2953 var buf: [longest_keyword_or_primitive_len]u8 = undefined;
2954
2955 contents_i = 0;
2956 var buf_i: usize = 0;
2957 while (contents_i < contents.len and buf_i < longest_keyword_or_primitive_len) {
2958 if (contents[contents_i] == '\\') {
2959 const res = std.zig.string_literal.parseEscapeSequence(contents, &contents_i).success;
2960 buf[buf_i] = @as(u8, @intCast(res));
2961 buf_i += 1;
2962 } else {
2963 buf[buf_i] = contents[contents_i];
2964 contents_i += 1;
2965 buf_i += 1;
2966 }
2967 }
2968
2969 // We read the whole thing, so it could be a keyword or primitive.
2970 if (contents_i == contents.len) {
2971 if (!std.zig.isValidId(buf[0..buf_i])) {
2972 return renderQuotedIdentifier(r, token_index, space, false);
2973 }
2974 if (primitives.isPrimitive(buf[0..buf_i])) switch (quote) {
2975 .eagerly_unquote,
2976 .eagerly_unquote_except_underscore,
2977 => return renderQuotedIdentifier(r, token_index, space, true),
2978 .preserve_when_shadowing => return renderQuotedIdentifier(r, token_index, space, false),
2979 };
2980 }
2981
2982 try renderQuotedIdentifier(r, token_index, space, true);
2983}
2984
2985// Renders a @"" quoted identifier, normalizing escapes.
2986// Unnecessary escapes are un-escaped, and \u escapes are normalized to \x when they fit.
2987// If unquote is true, the @"" is removed and the result is a bare symbol whose validity is asserted.
2988fn renderQuotedIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, comptime unquote: bool) !void {
2989 const tree = r.tree;
2990 const ais = r.ais;
2991 assert(tree.tokenTag(token_index) == .identifier);
2992 const lexeme = tokenSliceForRender(tree, token_index);
2993 assert(lexeme.len >= 3 and lexeme[0] == '@');
2994
2995 if (!unquote) try ais.writer().writeAll("@\"");
2996 const contents = lexeme[2 .. lexeme.len - 1];
2997 try renderIdentifierContents(ais.writer(), contents);
2998 if (!unquote) try ais.writer().writeByte('\"');
2999
3000 try renderSpace(r, token_index, lexeme.len, space);
3001}
3002
3003fn renderIdentifierContents(writer: anytype, bytes: []const u8) !void {
3004 var pos: usize = 0;
3005 while (pos < bytes.len) {
3006 const byte = bytes[pos];
3007 switch (byte) {
3008 '\\' => {
3009 const old_pos = pos;
3010 const res = std.zig.string_literal.parseEscapeSequence(bytes, &pos);
3011 const escape_sequence = bytes[old_pos..pos];
3012 switch (res) {
3013 .success => |codepoint| {
3014 if (codepoint <= 0x7f) {
3015 const buf = [1]u8{@as(u8, @intCast(codepoint))};
3016 try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
3017 } else {
3018 try writer.writeAll(escape_sequence);
3019 }
3020 },
3021 .failure => {
3022 try writer.writeAll(escape_sequence);
3023 },
3024 }
3025 },
3026 0x00...('\\' - 1), ('\\' + 1)...0x7f => {
3027 const buf = [1]u8{byte};
3028 try std.fmt.format(writer, "{f}", .{std.zig.fmtString(&buf)});
3029 pos += 1;
3030 },
3031 0x80...0xff => {
3032 try writer.writeByte(byte);
3033 pos += 1;
3034 },
3035 }
3036 }
3037}
3038
3039/// Returns true if there exists a line comment between any of the tokens from
3040/// `start_token` to `end_token`. This is used to determine if e.g. a
3041/// fn_proto should be wrapped and have a trailing comma inserted even if
3042/// there is none in the source.
3043fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3044 for (start_token..end_token) |i| {
3045 const token: Ast.TokenIndex = @intCast(i);
3046 const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
3047 const end = tree.tokenStart(token + 1);
3048 if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
3049 }
3050
3051 return false;
3052}
3053
3054/// Returns true if there exists a multiline string literal between the start
3055/// of token `start_token` and the start of token `end_token`.
3056fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3057 return std.mem.indexOfScalar(
3058 Token.Tag,
3059 tree.tokens.items(.tag)[start_token..end_token],
3060 .multiline_string_literal_line,
3061 ) != null;
3062}
3063
3064/// Assumes that start is the first byte past the previous token and
3065/// that end is the last byte before the next token.
3066fn renderComments(r: *Render, start: usize, end: usize) Error!bool {
3067 const tree = r.tree;
3068 const ais = r.ais;
3069
3070 var index: usize = start;
3071 while (mem.indexOf(u8, tree.source[index..end], "//")) |offset| {
3072 const comment_start = index + offset;
3073
3074 // If there is no newline, the comment ends with EOF
3075 const newline_index = mem.indexOfScalar(u8, tree.source[comment_start..end], '\n');
3076 const newline = if (newline_index) |i| comment_start + i else null;
3077
3078 const untrimmed_comment = tree.source[comment_start .. newline orelse tree.source.len];
3079 const trimmed_comment = mem.trimEnd(u8, untrimmed_comment, &std.ascii.whitespace);
3080
3081 // Don't leave any whitespace at the start of the file
3082 if (index != 0) {
3083 if (index == start and mem.containsAtLeast(u8, tree.source[index..comment_start], 2, "\n")) {
3084 // Leave up to one empty line before the first comment
3085 try ais.insertNewline();
3086 try ais.insertNewline();
3087 } else if (mem.indexOfScalar(u8, tree.source[index..comment_start], '\n') != null) {
3088 // Respect the newline directly before the comment.
3089 // Note: This allows an empty line between comments
3090 try ais.insertNewline();
3091 } else if (index == start) {
3092 // Otherwise if the first comment is on the same line as
3093 // the token before it, prefix it with a single space.
3094 try ais.writer().writeByte(' ');
3095 }
3096 }
3097
3098 index = 1 + (newline orelse end - 1);
3099
3100 const comment_content = mem.trimStart(u8, trimmed_comment["//".len..], &std.ascii.whitespace);
3101 if (ais.disabled_offset != null and mem.eql(u8, comment_content, "zig fmt: on")) {
3102 // Write the source for which formatting was disabled directly
3103 // to the underlying writer, fixing up invalid whitespace.
3104 const disabled_source = tree.source[ais.disabled_offset.?..comment_start];
3105 try writeFixingWhitespace(ais.underlying_writer, disabled_source);
3106 // Write with the canonical single space.
3107 try ais.underlying_writer.writeAll("// zig fmt: on\n");
3108 ais.disabled_offset = null;
3109 } else if (ais.disabled_offset == null and mem.eql(u8, comment_content, "zig fmt: off")) {
3110 // Write with the canonical single space.
3111 try ais.writer().writeAll("// zig fmt: off\n");
3112 ais.disabled_offset = index;
3113 } else {
3114 // Write the comment minus trailing whitespace.
3115 try ais.writer().print("{s}\n", .{trimmed_comment});
3116 }
3117 }
3118
3119 if (index != start and mem.containsAtLeast(u8, tree.source[index - 1 .. end], 2, "\n")) {
3120 // Don't leave any whitespace at the end of the file
3121 if (end != tree.source.len) {
3122 try ais.insertNewline();
3123 }
3124 }
3125
3126 return index != start;
3127}
3128
3129fn renderExtraNewline(r: *Render, node: Ast.Node.Index) Error!void {
3130 return renderExtraNewlineToken(r, r.tree.firstToken(node));
3131}
3132
3133/// Check if there is an empty line immediately before the given token. If so, render it.
3134fn renderExtraNewlineToken(r: *Render, token_index: Ast.TokenIndex) Error!void {
3135 const tree = r.tree;
3136 const ais = r.ais;
3137 const token_start = tree.tokenStart(token_index);
3138 if (token_start == 0) return;
3139 const prev_token_end = if (token_index == 0)
3140 0
3141 else
3142 tree.tokenStart(token_index - 1) + tokenSliceForRender(tree, token_index - 1).len;
3143
3144 // If there is a immediately preceding comment or doc_comment,
3145 // skip it because required extra newline has already been rendered.
3146 if (mem.indexOf(u8, tree.source[prev_token_end..token_start], "//") != null) return;
3147 if (tree.isTokenPrecededByTags(token_index, &.{.doc_comment})) return;
3148
3149 // Iterate backwards to the end of the previous token, stopping if a
3150 // non-whitespace character is encountered or two newlines have been found.
3151 var i = token_start - 1;
3152 var newlines: u2 = 0;
3153 while (std.ascii.isWhitespace(tree.source[i])) : (i -= 1) {
3154 if (tree.source[i] == '\n') newlines += 1;
3155 if (newlines == 2) return ais.insertNewline();
3156 if (i == prev_token_end) break;
3157 }
3158}
3159
3160/// end_token is the token one past the last doc comment token. This function
3161/// searches backwards from there.
3162fn renderDocComments(r: *Render, end_token: Ast.TokenIndex) Error!void {
3163 const tree = r.tree;
3164 // Search backwards for the first doc comment.
3165 if (end_token == 0) return;
3166 var tok = end_token - 1;
3167 while (tree.tokenTag(tok) == .doc_comment) {
3168 if (tok == 0) break;
3169 tok -= 1;
3170 } else {
3171 tok += 1;
3172 }
3173 const first_tok = tok;
3174 if (first_tok == end_token) return;
3175
3176 if (first_tok != 0) {
3177 const prev_token_tag = tree.tokenTag(first_tok - 1);
3178
3179 // Prevent accidental use of `renderDocComments` for a function argument doc comment
3180 assert(prev_token_tag != .l_paren);
3181
3182 if (prev_token_tag != .l_brace) {
3183 try renderExtraNewlineToken(r, first_tok);
3184 }
3185 }
3186
3187 while (tree.tokenTag(tok) == .doc_comment) : (tok += 1) {
3188 try renderToken(r, tok, .newline);
3189 }
3190}
3191
3192/// start_token is first container doc comment token.
3193fn renderContainerDocComments(r: *Render, start_token: Ast.TokenIndex) Error!void {
3194 const tree = r.tree;
3195 var tok = start_token;
3196 while (tree.tokenTag(tok) == .container_doc_comment) : (tok += 1) {
3197 try renderToken(r, tok, .newline);
3198 }
3199 // Render extra newline if there is one between final container doc comment and
3200 // the next token. If the next token is a doc comment, that code path
3201 // will have its own logic to insert a newline.
3202 if (tree.tokenTag(tok) != .doc_comment) {
3203 try renderExtraNewlineToken(r, tok);
3204 }
3205}
3206
3207fn discardAllParams(r: *Render, fn_proto_node: Ast.Node.Index) Error!void {
3208 const tree = &r.tree;
3209 const ais = r.ais;
3210 var buf: [1]Ast.Node.Index = undefined;
3211 const fn_proto = tree.fullFnProto(&buf, fn_proto_node).?;
3212 var it = fn_proto.iterate(tree);
3213 while (it.next()) |param| {
3214 const name_ident = param.name_token.?;
3215 assert(tree.tokenTag(name_ident) == .identifier);
3216 const w = ais.writer();
3217 try w.writeAll("_ = ");
3218 try w.writeAll(tokenSliceForRender(r.tree, name_ident));
3219 try w.writeAll(";\n");
3220 }
3221}
3222
3223fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 {
3224 var ret = tree.tokenSlice(token_index);
3225 switch (tree.tokenTag(token_index)) {
3226 .container_doc_comment, .doc_comment => {
3227 ret = mem.trimEnd(u8, ret, &std.ascii.whitespace);
3228 },
3229 else => {},
3230 }
3231 return ret;
3232}
3233
3234fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usize {
3235 const tree = r.tree;
3236 const ais = r.ais;
3237 assert(tree.tokenTag(token_index) == .string_literal);
3238 const lexeme = tokenSliceForRender(tree, token_index);
3239 const unquoted = lexeme[1..][0 .. lexeme.len - 2];
3240 if (std.zig.isValidId(unquoted)) {
3241 try ais.writer().writeAll(unquoted);
3242 return unquoted.len;
3243 } else {
3244 try ais.writer().writeByte('@');
3245 try ais.writer().writeAll(lexeme);
3246 return lexeme.len + 1;
3247 }
3248}
3249
3250fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3251 const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3252 for (between_source) |byte| switch (byte) {
3253 '\n' => return false,
3254 '/' => return true,
3255 else => continue,
3256 };
3257 return false;
3258}
3259
3260/// Returns `true` if and only if there are any tokens or line comments between
3261/// start_token and end_token.
3262fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
3263 if (start_token + 1 != end_token) return true;
3264 const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3265 for (between_source) |byte| switch (byte) {
3266 '/' => return true,
3267 else => continue,
3268 };
3269 return false;
3270}
3271
3272fn writeFixingWhitespace(writer: std.ArrayList(u8).Writer, slice: []const u8) Error!void {
3273 for (slice) |byte| switch (byte) {
3274 '\t' => try writer.writeAll(" " ** indent_delta),
3275 '\r' => {},
3276 else => try writer.writeByte(byte),
3277 };
3278}
3279
3280fn nodeIsBlock(tag: Ast.Node.Tag) bool {
3281 return switch (tag) {
3282 .block,
3283 .block_semicolon,
3284 .block_two,
3285 .block_two_semicolon,
3286 => true,
3287 else => false,
3288 };
3289}
3290
3291fn nodeIsIfForWhileSwitch(tag: Ast.Node.Tag) bool {
3292 return switch (tag) {
3293 .@"if",
3294 .if_simple,
3295 .@"for",
3296 .for_simple,
3297 .@"while",
3298 .while_simple,
3299 .while_cont,
3300 .@"switch",
3301 .switch_comma,
3302 => true,
3303 else => false,
3304 };
3305}
3306
3307fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
3308 return switch (tag) {
3309 .@"catch",
3310 .add,
3311 .add_wrap,
3312 .array_cat,
3313 .array_mult,
3314 .assign,
3315 .assign_bit_and,
3316 .assign_bit_or,
3317 .assign_shl,
3318 .assign_shr,
3319 .assign_bit_xor,
3320 .assign_div,
3321 .assign_sub,
3322 .assign_sub_wrap,
3323 .assign_mod,
3324 .assign_add,
3325 .assign_add_wrap,
3326 .assign_mul,
3327 .assign_mul_wrap,
3328 .bang_equal,
3329 .bit_and,
3330 .bit_or,
3331 .shl,
3332 .shr,
3333 .bit_xor,
3334 .bool_and,
3335 .bool_or,
3336 .div,
3337 .equal_equal,
3338 .error_union,
3339 .greater_or_equal,
3340 .greater_than,
3341 .less_or_equal,
3342 .less_than,
3343 .merge_error_sets,
3344 .mod,
3345 .mul,
3346 .mul_wrap,
3347 .sub,
3348 .sub_wrap,
3349 .@"orelse",
3350 => true,
3351
3352 else => false,
3353 };
3354}
3355
3356// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3357fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3358 const first_token = tree.firstToken(exprs[0]);
3359 if (tree.tokensOnSameLine(first_token, rtoken)) {
3360 const maybe_comma = rtoken - 1;
3361 if (tree.tokenTag(maybe_comma) == .comma)
3362 return 1;
3363 return exprs.len; // no newlines
3364 }
3365
3366 var count: usize = 1;
3367 for (exprs, 0..) |expr, i| {
3368 if (i + 1 < exprs.len) {
3369 const expr_last_token = tree.lastToken(expr) + 1;
3370 if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3371 count += 1;
3372 } else {
3373 return count;
3374 }
3375 }
3376 unreachable;
3377}
3378
3379/// Automatically inserts indentation of written data by keeping
3380/// track of the current indentation level
3381///
3382/// We introduce a new indentation scope with pushIndent/popIndent whenever
3383/// we potentially want to introduce an indent after the next newline.
3384///
3385/// Indentation should only ever increment by one from one line to the next,
3386/// no matter how many new indentation scopes are introduced. This is done by
3387/// only realizing the indentation from the most recent scope. As an example:
3388///
3389/// while (foo) if (bar)
3390/// f(x);
3391///
3392/// The body of `while` introduces a new indentation scope and the body of
3393/// `if` also introduces a new indentation scope. When the newline is seen,
3394/// only the indentation scope of the `if` is realized, and the `while` is
3395/// not.
3396///
3397/// As comments are rendered during space rendering, we need to keep track
3398/// of the appropriate indentation level for them with pushSpace/popSpace.
3399/// This should be done whenever a scope that ends in a .semicolon or a
3400/// .comma is introduced.
3401fn AutoIndentingStream(comptime UnderlyingWriter: type) type {
3402 return struct {
3403 const Self = @This();
3404 pub const WriteError = UnderlyingWriter.Error;
3405 pub const Writer = std.io.GenericWriter(*Self, WriteError, write);
3406
3407 pub const IndentType = enum {
3408 normal,
3409 after_equals,
3410 binop,
3411 field_access,
3412 };
3413 const StackElem = struct {
3414 indent_type: IndentType,
3415 realized: bool,
3416 };
3417 const SpaceElem = struct {
3418 space: Space,
3419 indent_count: usize,
3420 };
3421
3422 underlying_writer: UnderlyingWriter,
3423
3424 /// Offset into the source at which formatting has been disabled with
3425 /// a `zig fmt: off` comment.
3426 ///
3427 /// If non-null, the AutoIndentingStream will not write any bytes
3428 /// to the underlying writer. It will however continue to track the
3429 /// indentation level.
3430 disabled_offset: ?usize = null,
3431
3432 indent_count: usize = 0,
3433 indent_delta: usize,
3434 indent_stack: std.ArrayList(StackElem),
3435 space_stack: std.ArrayList(SpaceElem),
3436 space_mode: ?usize = null,
3437 disable_indent_committing: usize = 0,
3438 current_line_empty: bool = true,
3439 /// the most recently applied indent
3440 applied_indent: usize = 0,
3441
3442 pub fn init(buffer: *std.ArrayList(u8), indent_delta_: usize) Self {
3443 return .{
3444 .underlying_writer = buffer.writer(),
3445 .indent_delta = indent_delta_,
3446 .indent_stack = std.ArrayList(StackElem).init(buffer.allocator),
3447 .space_stack = std.ArrayList(SpaceElem).init(buffer.allocator),
3448 };
3449 }
3450
3451 pub fn deinit(self: *Self) void {
3452 self.indent_stack.deinit();
3453 self.space_stack.deinit();
3454 }
3455
3456 pub fn writer(self: *Self) Writer {
3457 return .{ .context = self };
3458 }
3459
3460 pub fn write(self: *Self, bytes: []const u8) WriteError!usize {
3461 if (bytes.len == 0)
3462 return @as(usize, 0);
3463
3464 try self.applyIndent();
3465 return self.writeNoIndent(bytes);
3466 }
3467
3468 // Change the indent delta without changing the final indentation level
3469 pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void {
3470 if (self.indent_delta == new_indent_delta) {
3471 return;
3472 } else if (self.indent_delta > new_indent_delta) {
3473 assert(self.indent_delta % new_indent_delta == 0);
3474 self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta);
3475 } else {
3476 // assert that the current indentation (in spaces) in a multiple of the new delta
3477 assert((self.indent_count * self.indent_delta) % new_indent_delta == 0);
3478 self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta);
3479 }
3480 self.indent_delta = new_indent_delta;
3481 }
3482
3483 fn writeNoIndent(self: *Self, bytes: []const u8) WriteError!usize {
3484 if (bytes.len == 0)
3485 return @as(usize, 0);
3486
3487 if (self.disabled_offset == null) try self.underlying_writer.writeAll(bytes);
3488 if (bytes[bytes.len - 1] == '\n')
3489 self.resetLine();
3490 return bytes.len;
3491 }
3492
3493 pub fn insertNewline(self: *Self) WriteError!void {
3494 _ = try self.writeNoIndent("\n");
3495 }
3496
3497 fn resetLine(self: *Self) void {
3498 self.current_line_empty = true;
3499
3500 if (self.disable_indent_committing > 0) return;
3501
3502 if (self.indent_stack.items.len > 0) {
3503 // By default, we realize the most recent indentation scope.
3504 var to_realize = self.indent_stack.items.len - 1;
3505
3506 if (self.indent_stack.items.len >= 2 and
3507 self.indent_stack.items[to_realize - 1].indent_type == .after_equals and
3508 self.indent_stack.items[to_realize - 1].realized and
3509 self.indent_stack.items[to_realize].indent_type == .binop)
3510 {
3511 // If we are in a .binop scope and our direct parent is .after_equals, don't indent.
3512 // This ensures correct indentation in the below example:
3513 //
3514 // const foo =
3515 // (x >= 'a' and x <= 'z') or //<-- we are here
3516 // (x >= 'A' and x <= 'Z');
3517 //
3518 return;
3519 }
3520
3521 if (self.indent_stack.items[to_realize].indent_type == .field_access) {
3522 // Only realize the top-most field_access in a chain.
3523 while (to_realize > 0 and self.indent_stack.items[to_realize - 1].indent_type == .field_access)
3524 to_realize -= 1;
3525 }
3526
3527 if (self.indent_stack.items[to_realize].realized) return;
3528 self.indent_stack.items[to_realize].realized = true;
3529 self.indent_count += 1;
3530 }
3531 }
3532
3533 /// Disables indentation level changes during the next newlines until re-enabled.
3534 pub fn disableIndentCommitting(self: *Self) void {
3535 self.disable_indent_committing += 1;
3536 }
3537
3538 pub fn enableIndentCommitting(self: *Self) void {
3539 assert(self.disable_indent_committing > 0);
3540 self.disable_indent_committing -= 1;
3541 }
3542
3543 pub fn pushSpace(self: *Self, space: Space) !void {
3544 try self.space_stack.append(.{ .space = space, .indent_count = self.indent_count });
3545 }
3546
3547 pub fn popSpace(self: *Self) void {
3548 _ = self.space_stack.pop();
3549 }
3550
3551 /// Sets current indentation level to be the same as that of the last pushSpace.
3552 pub fn enableSpaceMode(self: *Self, space: Space) void {
3553 if (self.space_stack.items.len == 0) return;
3554 const curr = self.space_stack.getLast();
3555 if (curr.space != space) return;
3556 self.space_mode = curr.indent_count;
3557 }
3558
3559 pub fn disableSpaceMode(self: *Self) void {
3560 self.space_mode = null;
3561 }
3562
3563 pub fn lastSpaceModeIndent(self: *Self) usize {
3564 if (self.space_stack.items.len == 0) return 0;
3565 return self.space_stack.getLast().indent_count * self.indent_delta;
3566 }
3567
3568 /// Insert a newline unless the current line is blank
3569 pub fn maybeInsertNewline(self: *Self) WriteError!void {
3570 if (!self.current_line_empty)
3571 try self.insertNewline();
3572 }
3573
3574 /// Push default indentation
3575 /// Doesn't actually write any indentation.
3576 /// Just primes the stream to be able to write the correct indentation if it needs to.
3577 pub fn pushIndent(self: *Self, indent_type: IndentType) !void {
3578 try self.indent_stack.append(.{ .indent_type = indent_type, .realized = false });
3579 }
3580
3581 /// Forces an indentation level to be realized.
3582 pub fn forcePushIndent(self: *Self, indent_type: IndentType) !void {
3583 try self.indent_stack.append(.{ .indent_type = indent_type, .realized = true });
3584 self.indent_count += 1;
3585 }
3586
3587 pub fn popIndent(self: *Self) void {
3588 if (self.indent_stack.pop().?.realized) {
3589 assert(self.indent_count > 0);
3590 self.indent_count -= 1;
3591 }
3592 }
3593
3594 pub fn indentStackEmpty(self: *Self) bool {
3595 return self.indent_stack.items.len == 0;
3596 }
3597
3598 /// Writes ' ' bytes if the current line is empty
3599 fn applyIndent(self: *Self) WriteError!void {
3600 const current_indent = self.currentIndent();
3601 if (self.current_line_empty and current_indent > 0) {
3602 if (self.disabled_offset == null) {
3603 try self.underlying_writer.writeByteNTimes(' ', current_indent);
3604 }
3605 self.applied_indent = current_indent;
3606 }
3607 self.current_line_empty = false;
3608 }
3609
3610 /// Checks to see if the most recent indentation exceeds the currently pushed indents
3611 pub fn isLineOverIndented(self: *Self) bool {
3612 if (self.current_line_empty) return false;
3613 return self.applied_indent > self.currentIndent();
3614 }
3615
3616 fn currentIndent(self: *Self) usize {
3617 const indent_count = self.space_mode orelse self.indent_count;
3618 return indent_count * self.indent_delta;
3619 }
3620 };
3621}
lib/std/zig/string_literal.zig+15-11
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;2const assert = std.debug.assert;
3const utf8Encode = std.unicode.utf8Encode;3const utf8Encode = std.unicode.utf8Encode;
4const Writer = std.io.Writer;
45
5pub const ParseError = error{6pub const ParseError = error{
6 OutOfMemory,7 OutOfMemory,
...@@ -315,9 +316,10 @@ test parseCharLiteral {...@@ -315,9 +316,10 @@ test parseCharLiteral {
315 );316 );
316}317}
317318
318/// Parses `bytes` as a Zig string literal and writes the result to the `std.io.GenericWriter` type.319/// Parses `bytes` as a Zig string literal and writes the result to the `Writer` type.
320///
319/// Asserts `bytes` has '"' at beginning and end.321/// Asserts `bytes` has '"' at beginning and end.
320pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result {322pub fn parseWrite(writer: *Writer, bytes: []const u8) Writer.Error!Result {
321 assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');323 assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');
322324
323 var index: usize = 1;325 var index: usize = 1;
...@@ -333,18 +335,18 @@ pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result...@@ -333,18 +335,18 @@ pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result
333 if (bytes[escape_char_index] == 'u') {335 if (bytes[escape_char_index] == 'u') {
334 var buf: [4]u8 = undefined;336 var buf: [4]u8 = undefined;
335 const len = utf8Encode(codepoint, &buf) catch {337 const len = utf8Encode(codepoint, &buf) catch {
336 return Result{ .failure = .{ .invalid_unicode_codepoint = escape_char_index + 1 } };338 return .{ .failure = .{ .invalid_unicode_codepoint = escape_char_index + 1 } };
337 };339 };
338 try writer.writeAll(buf[0..len]);340 try writer.writeAll(buf[0..len]);
339 } else {341 } else {
340 try writer.writeByte(@as(u8, @intCast(codepoint)));342 try writer.writeByte(@as(u8, @intCast(codepoint)));
341 }343 }
342 },344 },
343 .failure => |err| return Result{ .failure = err },345 .failure => |err| return .{ .failure = err },
344 }346 }
345 },347 },
346 '\n' => return Result{ .failure = .{ .invalid_character = index } },348 '\n' => return .{ .failure = .{ .invalid_character = index } },
347 '"' => return Result.success,349 '"' => return .success,
348 else => {350 else => {
349 try writer.writeByte(b);351 try writer.writeByte(b);
350 index += 1;352 index += 1;
...@@ -356,11 +358,13 @@ pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result...@@ -356,11 +358,13 @@ pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result
356/// Higher level API. Does not return extra info about parse errors.358/// Higher level API. Does not return extra info about parse errors.
357/// Caller owns returned memory.359/// Caller owns returned memory.
358pub fn parseAlloc(allocator: std.mem.Allocator, bytes: []const u8) ParseError![]u8 {360pub fn parseAlloc(allocator: std.mem.Allocator, bytes: []const u8) ParseError![]u8 {
359 var buf = std.ArrayList(u8).init(allocator);361 var aw: std.io.Writer.Allocating = .init(allocator);
360 defer buf.deinit();362 defer aw.deinit();
361363 const result = parseWrite(&aw.writer, bytes) catch |err| switch (err) {
362 switch (try parseWrite(buf.writer(), bytes)) {364 error.WriteFailed => return error.OutOfMemory,
363 .success => return buf.toOwnedSlice(),365 };
366 switch (result) {
367 .success => return aw.toOwnedSlice(),
364 .failure => return error.InvalidLiteral,368 .failure => return error.InvalidLiteral,
365 }369 }
366}370}
lib/std/zon/parse.zig+14-8
...@@ -411,18 +411,22 @@ const Parser = struct {...@@ -411,18 +411,22 @@ const Parser = struct {
411 diag: ?*Diagnostics,411 diag: ?*Diagnostics,
412 options: Options,412 options: Options,
413413
414 fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory }!T {414 const ParseExprError = error{ ParseZon, OutOfMemory };
415
416 fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprError!T {
415 return self.parseExprInner(T, node) catch |err| switch (err) {417 return self.parseExprInner(T, node) catch |err| switch (err) {
416 error.WrongType => return self.failExpectedType(T, node),418 error.WrongType => return self.failExpectedType(T, node),
417 else => |e| return e,419 else => |e| return e,
418 };420 };
419 }421 }
420422
423 const ParseExprInnerError = error{ ParseZon, OutOfMemory, WrongType };
424
421 fn parseExprInner(425 fn parseExprInner(
422 self: *@This(),426 self: *@This(),
423 T: type,427 T: type,
424 node: Zoir.Node.Index,428 node: Zoir.Node.Index,
425 ) error{ ParseZon, OutOfMemory, WrongType }!T {429 ) ParseExprInnerError!T {
426 if (T == Zoir.Node.Index) {430 if (T == Zoir.Node.Index) {
427 return node;431 return node;
428 }432 }
...@@ -611,15 +615,17 @@ const Parser = struct {...@@ -611,15 +615,17 @@ const Parser = struct {
611 }615 }
612 }616 }
613617
614 fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) !T {618 fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T {
615 const ast_node = node.getAstNode(self.zoir);619 const ast_node = node.getAstNode(self.zoir);
616 const pointer = @typeInfo(T).pointer;620 const pointer = @typeInfo(T).pointer;
617 var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node);621 var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node);
618 if (pointer.sentinel() != null) size_hint += 1;622 if (pointer.sentinel() != null) size_hint += 1;
619623
620 var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint);624 var aw: std.Io.Writer.Allocating = .init(self.gpa);
621 defer buf.deinit(self.gpa);625 try aw.ensureUnusedCapacity(size_hint);
622 switch (try ZonGen.parseStrLit(self.ast, ast_node, buf.writer(self.gpa))) {626 defer aw.deinit();
627 const result = ZonGen.parseStrLit(self.ast, ast_node, &aw.writer) catch return error.OutOfMemory;
628 switch (result) {
623 .success => {},629 .success => {},
624 .failure => |err| {630 .failure => |err| {
625 const token = self.ast.nodeMainToken(ast_node);631 const token = self.ast.nodeMainToken(ast_node);
...@@ -638,9 +644,9 @@ const Parser = struct {...@@ -638,9 +644,9 @@ const Parser = struct {
638 }644 }
639645
640 if (pointer.sentinel() != null) {646 if (pointer.sentinel() != null) {
641 return buf.toOwnedSliceSentinel(self.gpa, 0);647 return aw.toOwnedSliceSentinel(0);
642 } else {648 } else {
643 return buf.toOwnedSlice(self.gpa);649 return aw.toOwnedSlice();
644 }650 }
645 }651 }
646652
src/Compilation.zig+1-1
...@@ -5328,7 +5328,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module...@@ -5328,7 +5328,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module
5328 var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{});5328 var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{});
5329 defer out_zig_file.close();5329 defer out_zig_file.close();
53305330
5331 const formatted = try tree.render(comp.gpa);5331 const formatted = try tree.renderAlloc(comp.gpa);
5332 defer comp.gpa.free(formatted);5332 defer comp.gpa.free(formatted);
53335333
5334 try out_zig_file.writeAll(formatted);5334 try out_zig_file.writeAll(formatted);
src/Package/Manifest.zig+8-4
...@@ -471,10 +471,14 @@ const Parse = struct {...@@ -471,10 +471,14 @@ const Parse = struct {
471 offset: u32,471 offset: u32,
472 ) InnerError!void {472 ) InnerError!void {
473 const raw_string = bytes[offset..];473 const raw_string = bytes[offset..];
474 var buf_managed = buf.toManaged(p.gpa);474 const result = r: {
475 const result = std.zig.string_literal.parseWrite(buf_managed.writer(), raw_string);475 var aw: std.io.Writer.Allocating = .fromArrayList(p.gpa, buf);
476 buf.* = buf_managed.moveToUnmanaged();476 defer buf.* = aw.toArrayList();
477 switch (try result) {477 break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) {
478 error.WriteFailed => return error.OutOfMemory,
479 };
480 };
481 switch (result) {
478 .success => {},482 .success => {},
479 .failure => |err| try p.appendStrLitError(err, token, bytes, offset),483 .failure => |err| try p.appendStrLitError(err, token, bytes, offset),
480 }484 }
src/fmt.zig+43-62
...@@ -34,16 +34,13 @@ const Fmt = struct {...@@ -34,16 +34,13 @@ const Fmt = struct {
34 color: Color,34 color: Color,
35 gpa: Allocator,35 gpa: Allocator,
36 arena: Allocator,36 arena: Allocator,
37 out_buffer: std.ArrayList(u8),37 out_buffer: std.Io.Writer.Allocating,
38 stdout_writer: *fs.File.Writer,
3839
39 const SeenMap = std.AutoHashMap(fs.File.INode, void);40 const SeenMap = std.AutoHashMap(fs.File.INode, void);
40};41};
4142
42pub fn run(43pub fn run(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
43 gpa: Allocator,
44 arena: Allocator,
45 args: []const []const u8,
46) !void {
47 var color: Color = .auto;44 var color: Color = .auto;
48 var stdin_flag = false;45 var stdin_flag = false;
49 var check_flag = false;46 var check_flag = false;
...@@ -60,8 +57,7 @@ pub fn run(...@@ -60,8 +57,7 @@ pub fn run(
60 const arg = args[i];57 const arg = args[i];
61 if (mem.startsWith(u8, arg, "-")) {58 if (mem.startsWith(u8, arg, "-")) {
62 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {59 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
63 const stdout = std.fs.File.stdout().deprecatedWriter();60 try fs.File.stdout().writeAll(usage_fmt);
64 try stdout.writeAll(usage_fmt);
65 return process.cleanExit();61 return process.cleanExit();
66 } else if (mem.eql(u8, arg, "--color")) {62 } else if (mem.eql(u8, arg, "--color")) {
67 if (i + 1 >= args.len) {63 if (i + 1 >= args.len) {
...@@ -102,7 +98,9 @@ pub fn run(...@@ -102,7 +98,9 @@ pub fn run(
102 }98 }
10399
104 const stdin: fs.File = .stdin();100 const stdin: fs.File = .stdin();
105 const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| {101 var stdio_buffer: [1024]u8 = undefined;
102 var file_reader: fs.File.Reader = stdin.reader(&stdio_buffer);
103 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| {
106 fatal("unable to read stdin: {}", .{err});104 fatal("unable to read stdin: {}", .{err});
107 };105 };
108 defer gpa.free(source_code);106 defer gpa.free(source_code);
...@@ -146,7 +144,7 @@ pub fn run(...@@ -146,7 +144,7 @@ pub fn run(
146 try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color);144 try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color);
147 process.exit(2);145 process.exit(2);
148 }146 }
149 const formatted = try tree.render(gpa);147 const formatted = try tree.renderAlloc(gpa);
150 defer gpa.free(formatted);148 defer gpa.free(formatted);
151149
152 if (check_flag) {150 if (check_flag) {
...@@ -154,13 +152,16 @@ pub fn run(...@@ -154,13 +152,16 @@ pub fn run(
154 process.exit(code);152 process.exit(code);
155 }153 }
156154
157 return std.fs.File.stdout().writeAll(formatted);155 return fs.File.stdout().writeAll(formatted);
158 }156 }
159157
160 if (input_files.items.len == 0) {158 if (input_files.items.len == 0) {
161 fatal("expected at least one source file argument", .{});159 fatal("expected at least one source file argument", .{});
162 }160 }
163161
162 var stdout_buffer: [4096]u8 = undefined;
163 var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
164
164 var fmt: Fmt = .{165 var fmt: Fmt = .{
165 .gpa = gpa,166 .gpa = gpa,
166 .arena = arena,167 .arena = arena,
...@@ -169,7 +170,8 @@ pub fn run(...@@ -169,7 +170,8 @@ pub fn run(
169 .check_ast = check_ast_flag,170 .check_ast = check_ast_flag,
170 .force_zon = force_zon,171 .force_zon = force_zon,
171 .color = color,172 .color = color,
172 .out_buffer = std.ArrayList(u8).init(gpa),173 .out_buffer = .init(gpa),
174 .stdout_writer = &stdout_writer,
173 };175 };
174 defer fmt.seen.deinit();176 defer fmt.seen.deinit();
175 defer fmt.out_buffer.deinit();177 defer fmt.out_buffer.deinit();
...@@ -196,43 +198,10 @@ pub fn run(...@@ -196,43 +198,10 @@ pub fn run(
196 if (fmt.any_error) {198 if (fmt.any_error) {
197 process.exit(1);199 process.exit(1);
198 }200 }
201 try fmt.stdout_writer.interface.flush();
199}202}
200203
201const FmtError = error{204fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) !void {
202 SystemResources,
203 OperationAborted,
204 IoPending,
205 BrokenPipe,
206 Unexpected,
207 WouldBlock,
208 Canceled,
209 FileClosed,
210 DestinationAddressRequired,
211 DiskQuota,
212 FileTooBig,
213 MessageTooBig,
214 InputOutput,
215 NoSpaceLeft,
216 AccessDenied,
217 OutOfMemory,
218 RenameAcrossMountPoints,
219 ReadOnlyFileSystem,
220 LinkQuotaExceeded,
221 FileBusy,
222 EndOfStream,
223 Unseekable,
224 NotOpenForWriting,
225 UnsupportedEncoding,
226 InvalidEncoding,
227 ConnectionResetByPeer,
228 SocketNotConnected,
229 LockViolation,
230 NetNameDeleted,
231 InvalidArgument,
232 ProcessNotFound,
233} || fs.File.OpenError;
234
235fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void {
236 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {205 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
237 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),206 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
238 else => {207 else => {
...@@ -249,7 +218,7 @@ fn fmtPathDir(...@@ -249,7 +218,7 @@ fn fmtPathDir(
249 check_mode: bool,218 check_mode: bool,
250 parent_dir: fs.Dir,219 parent_dir: fs.Dir,
251 parent_sub_path: []const u8,220 parent_sub_path: []const u8,
252) FmtError!void {221) !void {
253 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });222 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
254 defer dir.close();223 defer dir.close();
255224
...@@ -285,7 +254,7 @@ fn fmtPathFile(...@@ -285,7 +254,7 @@ fn fmtPathFile(
285 check_mode: bool,254 check_mode: bool,
286 dir: fs.Dir,255 dir: fs.Dir,
287 sub_path: []const u8,256 sub_path: []const u8,
288) FmtError!void {257) !void {
289 const source_file = try dir.openFile(sub_path, .{});258 const source_file = try dir.openFile(sub_path, .{});
290 var file_closed = false;259 var file_closed = false;
291 errdefer if (!file_closed) source_file.close();260 errdefer if (!file_closed) source_file.close();
...@@ -295,12 +264,15 @@ fn fmtPathFile(...@@ -295,12 +264,15 @@ fn fmtPathFile(
295 if (stat.kind == .directory)264 if (stat.kind == .directory)
296 return error.IsDir;265 return error.IsDir;
297266
267 var read_buffer: [1024]u8 = undefined;
268 var file_reader: fs.File.Reader = source_file.reader(&read_buffer);
269 file_reader.size = stat.size;
270
298 const gpa = fmt.gpa;271 const gpa = fmt.gpa;
299 const source_code = try std.zig.readSourceFileToEndAlloc(272 const source_code = std.zig.readSourceFileToEndAlloc(gpa, &file_reader) catch |err| switch (err) {
300 gpa,273 error.ReadFailed => return file_reader.err.?,
301 source_file,274 else => |e| return e,
302 std.math.cast(usize, stat.size) orelse return error.FileTooBig,275 };
303 );
304 defer gpa.free(source_code);276 defer gpa.free(source_code);
305277
306 source_file.close();278 source_file.close();
...@@ -363,24 +335,33 @@ fn fmtPathFile(...@@ -363,24 +335,33 @@ fn fmtPathFile(
363 }335 }
364336
365 // As a heuristic, we make enough capacity for the same as the input source.337 // As a heuristic, we make enough capacity for the same as the input source.
366 fmt.out_buffer.shrinkRetainingCapacity(0);338 fmt.out_buffer.clearRetainingCapacity();
367 try fmt.out_buffer.ensureTotalCapacity(source_code.len);339 try fmt.out_buffer.ensureTotalCapacity(source_code.len);
368340
369 try tree.renderToArrayList(&fmt.out_buffer, .{});341 tree.render(gpa, &fmt.out_buffer.writer, .{}) catch |err| switch (err) {
370 if (mem.eql(u8, fmt.out_buffer.items, source_code))342 error.WriteFailed, error.OutOfMemory => return error.OutOfMemory,
343 };
344 if (mem.eql(u8, fmt.out_buffer.getWritten(), source_code))
371 return;345 return;
372346
373 if (check_mode) {347 if (check_mode) {
374 const stdout = std.fs.File.stdout().deprecatedWriter();348 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
375 try stdout.print("{s}\n", .{file_path});
376 fmt.any_error = true;349 fmt.any_error = true;
377 } else {350 } else {
378 var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode });351 var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode });
379 defer af.deinit();352 defer af.deinit();
380353
381 try af.file.writeAll(fmt.out_buffer.items);354 try af.file.writeAll(fmt.out_buffer.getWritten());
382 try af.finish();355 try af.finish();
383 const stdout = std.fs.File.stdout().deprecatedWriter();356 try fmt.stdout_writer.interface.print("{s}\n", .{file_path});
384 try stdout.print("{s}\n", .{file_path});
385 }357 }
386}358}
359
360/// Provided for debugging/testing purposes; unused by the compiler.
361pub fn main() !void {
362 const gpa = std.heap.smp_allocator;
363 var arena_instance = std.heap.ArenaAllocator.init(gpa);
364 const arena = arena_instance.allocator();
365 const args = try process.argsAlloc(arena);
366 return run(gpa, arena, args[1..]);
367}
src/main.zig+14-10
...@@ -4550,7 +4550,7 @@ fn cmdTranslateC(...@@ -4550,7 +4550,7 @@ fn cmdTranslateC(
4550 error.SemanticAnalyzeFail => break :f .{ .error_bundle = errors },4550 error.SemanticAnalyzeFail => break :f .{ .error_bundle = errors },
4551 };4551 };
4552 defer tree.deinit(comp.gpa);4552 defer tree.deinit(comp.gpa);
4553 break :f .{ .success = try tree.render(arena) };4553 break :f .{ .success = try tree.renderAlloc(arena) };
4554 },4554 },
4555 };4555 };
45564556
...@@ -6058,7 +6058,8 @@ fn cmdAstCheck(...@@ -6058,7 +6058,8 @@ fn cmdAstCheck(
6058 };6058 };
6059 } else fs.File.stdin();6059 } else fs.File.stdin();
6060 defer if (zig_source_path != null) f.close();6060 defer if (zig_source_path != null) f.close();
6061 break :s std.zig.readSourceFileToEndAlloc(arena, f, null) catch |err| {6061 var file_reader: fs.File.Reader = f.reader(&stdio_buffer);
6062 break :s std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err| {
6062 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });6063 fatal("unable to load file '{s}' for ast-check: {s}", .{ display_path, @errorName(err) });
6063 };6064 };
6064 };6065 };
...@@ -6416,14 +6417,16 @@ fn cmdChangelist(...@@ -6416,14 +6417,16 @@ fn cmdChangelist(
6416 var f = fs.cwd().openFile(old_source_path, .{}) catch |err|6417 var f = fs.cwd().openFile(old_source_path, .{}) catch |err|
6417 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });6418 fatal("unable to open old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6418 defer f.close();6419 defer f.close();
6419 break :source std.zig.readSourceFileToEndAlloc(arena, f, std.zig.max_src_size) catch |err|6420 var file_reader: fs.File.Reader = f.reader(&stdio_buffer);
6421 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
6420 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });6422 fatal("unable to read old source file '{s}': {s}", .{ old_source_path, @errorName(err) });
6421 };6423 };
6422 const new_source = source: {6424 const new_source = source: {
6423 var f = fs.cwd().openFile(new_source_path, .{}) catch |err|6425 var f = fs.cwd().openFile(new_source_path, .{}) catch |err|
6424 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });6426 fatal("unable to open new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6425 defer f.close();6427 defer f.close();
6426 break :source std.zig.readSourceFileToEndAlloc(arena, f, std.zig.max_src_size) catch |err|6428 var file_reader: fs.File.Reader = f.reader(&stdio_buffer);
6429 break :source std.zig.readSourceFileToEndAlloc(arena, &file_reader) catch |err|
6427 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });6430 fatal("unable to read new source file '{s}': {s}", .{ new_source_path, @errorName(err) });
6428 };6431 };
64296432
...@@ -6946,7 +6949,7 @@ fn cmdFetch(...@@ -6946,7 +6949,7 @@ fn cmdFetch(
6946 ast.deinit(gpa);6949 ast.deinit(gpa);
6947 }6950 }
69486951
6949 var fixups: Ast.Fixups = .{};6952 var fixups: Ast.Render.Fixups = .{};
6950 defer fixups.deinit(gpa);6953 defer fixups.deinit(gpa);
69516954
6952 var saved_path_or_url = path_or_url;6955 var saved_path_or_url = path_or_url;
...@@ -7047,12 +7050,13 @@ fn cmdFetch(...@@ -7047,12 +7050,13 @@ fn cmdFetch(
7047 try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);7050 try fixups.append_string_after_node.put(gpa, manifest.version_node, dependencies_text);
7048 }7051 }
70497052
7050 var rendered = std.ArrayList(u8).init(gpa);7053 var aw: std.Io.Writer.Allocating = .init(gpa);
7051 defer rendered.deinit();7054 defer aw.deinit();
7052 try ast.renderToArrayList(&rendered, fixups);7055 try ast.render(gpa, &aw.writer, fixups);
7056 const rendered = aw.getWritten();
70537057
7054 build_root.directory.handle.writeFile(.{ .sub_path = Package.Manifest.basename, .data = rendered.items }) catch |err| {7058 build_root.directory.handle.writeFile(.{ .sub_path = Package.Manifest.basename, .data = rendered }) catch |err| {
7055 fatal("unable to write {s} file: {s}", .{ Package.Manifest.basename, @errorName(err) });7059 fatal("unable to write {s} file: {t}", .{ Package.Manifest.basename, err });
7056 };7060 };
70577061
7058 return cleanExit();7062 return cleanExit();
tools/gen_spirv_spec.zig+1-1
...@@ -120,7 +120,7 @@ pub fn main() !void {...@@ -120,7 +120,7 @@ pub fn main() !void {
120 error_bundle.renderToStdErr(color.renderOptions());120 error_bundle.renderToStdErr(color.renderOptions());
121 }121 }
122122
123 const formatted_output = try tree.render(allocator);123 const formatted_output = try tree.renderAlloc(allocator);
124 _ = try std.fs.File.stdout().write(formatted_output);124 _ = try std.fs.File.stdout().write(formatted_output);
125}125}
126126