Skip to content

Commit 5d32c3a

Browse files
committed
zig fmt: rewrite renderArrayInit
There were just too many bugs. This new implementation supports zig fmt: on/off. It also puts expressions with unicode characters on their own line, which avoids issues with aligning them.
1 parent 0792f4c commit 5d32c3a

2 files changed

Lines changed: 234 additions & 188 deletions

File tree

lib/std/zig/Ast/Render.zig

Lines changed: 115 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,170 +2467,132 @@ fn renderArrayInit(
24672467

24682468
try ais.pushIndent(.normal);
24692469
try renderToken(r, array_init.ast.lbrace, .newline);
2470-
2471-
var expr_index: usize = 0;
2472-
while (true) {
2473-
const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2474-
const row_exprs = array_init.ast.elements[expr_index..];
2475-
// A place to store the width of each expression and its column's maximum
2476-
const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2477-
defer gpa.free(widths);
2478-
@memset(widths, 0);
2479-
2480-
const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2481-
defer gpa.free(expr_newlines);
2482-
@memset(expr_newlines, false);
2483-
2484-
const expr_widths = widths[0..row_exprs.len];
2485-
const column_widths = widths[row_exprs.len..];
2486-
2487-
// Find next row with trailing comment (if any) to end the current section.
2488-
const section_end = sec_end: {
2489-
var this_line_first_expr: usize = 0;
2490-
var this_line_size = rowSize(tree, row_exprs, rbrace);
2491-
for (row_exprs, 0..) |expr, i| {
2492-
// Ignore comment on first line of this section.
2493-
if (i == 0) continue;
2494-
const expr_last_token = tree.lastToken(expr);
2495-
if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2496-
continue;
2497-
// Track start of line containing comment.
2498-
if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2499-
this_line_first_expr = i;
2500-
this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2501-
}
2502-
2503-
const maybe_comma = expr_last_token + 1;
2504-
if (tree.tokenTag(maybe_comma) == .comma) {
2505-
if (hasSameLineComment(tree, maybe_comma))
2506-
break :sec_end i - this_line_size + 1;
2507-
}
2508-
}
2509-
break :sec_end row_exprs.len;
2510-
};
2511-
expr_index += section_end;
2512-
2513-
const section_exprs = row_exprs[0..section_end];
2514-
2515-
var sub_expr_buffer: Writer.Allocating = .init(gpa);
2516-
defer sub_expr_buffer.deinit();
2517-
2518-
const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2519-
defer gpa.free(sub_expr_buffer_starts);
2520-
2521-
var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.writer, indent_delta);
2522-
defer auto_indenting_stream.deinit();
2523-
var sub_render: Render = .{
2470+
try ais.pushSpace(.comma);
2471+
2472+
const expr_widths = try gpa.alloc(enum(usize) {
2473+
/// The expression contains non-printable characters (e.g. unicode / newlines)
2474+
/// or has formatting disabled at the start or end.
2475+
nonprint = std.math.maxInt(usize),
2476+
_,
2477+
}, array_init.ast.elements.len);
2478+
defer gpa.free(expr_widths);
2479+
{
2480+
var buf: Writer.Allocating = .init(gpa);
2481+
defer buf.deinit();
2482+
var sub_ais: AutoIndentingStream = .init(gpa, &buf.writer, indent_delta);
2483+
sub_ais.disabled_offset = ais.disabled_offset;
2484+
defer sub_ais.deinit();
2485+
var sub_r: Render = .{
25242486
.gpa = r.gpa,
2525-
.ais = &auto_indenting_stream,
2487+
.ais = &sub_ais,
25262488
.tree = r.tree,
25272489
.fixups = r.fixups,
25282490
};
2529-
2530-
// Calculate size of columns in current section
2531-
var column_counter: usize = 0;
2532-
var single_line = true;
2533-
var contains_newline = false;
2534-
for (section_exprs, 0..) |expr, i| {
2535-
const start = sub_expr_buffer.written().len;
2536-
sub_expr_buffer_starts[i] = start;
2537-
2538-
if (i + 1 < section_exprs.len) {
2539-
try renderExpression(&sub_render, expr, .none);
2540-
const written = sub_expr_buffer.written();
2541-
const width = written.len - start;
2542-
const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null;
2543-
contains_newline = contains_newline or this_contains_newline;
2544-
expr_widths[i] = width;
2545-
expr_newlines[i] = this_contains_newline;
2546-
2547-
if (!this_contains_newline) {
2548-
const column = column_counter % row_size;
2549-
column_widths[column] = @max(column_widths[column], width);
2550-
2551-
const expr_last_token = tree.lastToken(expr) + 1;
2552-
const next_expr = section_exprs[i + 1];
2553-
column_counter += 1;
2554-
if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2555-
} else {
2556-
single_line = false;
2557-
column_counter = 0;
2558-
}
2491+
for (array_init.ast.elements, expr_widths) |e, *width| {
2492+
const begin_disabled = sub_ais.disabled_offset != null;
2493+
// `.skip` space so trailing commments aren't included
2494+
try renderExpressionComma(&sub_r, e, .skip);
2495+
if (!begin_disabled and sub_ais.disabled_offset == null) {
2496+
const w = buf.written();
2497+
width.* = for (w) |c| {
2498+
if (!std.ascii.isPrint(c))
2499+
break .nonprint;
2500+
} else @enumFromInt(w.len - @intFromBool(w[w.len - 1] == ','));
25592501
} else {
2560-
try ais.pushSpace(.comma);
2561-
try renderExpression(&sub_render, expr, .comma);
2562-
ais.popSpace();
2563-
2564-
const written = sub_expr_buffer.written();
2565-
const width = written.len - start - 2;
2566-
const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null;
2567-
contains_newline = contains_newline or this_contains_newline;
2568-
expr_widths[i] = width;
2569-
expr_newlines[i] = contains_newline;
2570-
2571-
if (!contains_newline) {
2572-
const column = column_counter % row_size;
2573-
column_widths[column] = @max(column_widths[column], width);
2574-
}
2502+
width.* = .nonprint;
25752503
}
2576-
}
2577-
sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.written().len;
25782504

2579-
// Render exprs in current section.
2580-
column_counter = 0;
2581-
for (section_exprs, 0..) |expr, i| {
2582-
const start = sub_expr_buffer_starts[i];
2583-
const end = sub_expr_buffer_starts[i + 1];
2584-
const expr_text = sub_expr_buffer.written()[start..end];
2585-
if (!expr_newlines[i]) {
2586-
try ais.writeAll(expr_text);
2587-
} else {
2588-
var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2589-
var last_line_was_empty = false;
2590-
try ais.writeAll(by_line.first());
2591-
while (by_line.next()) |line| {
2592-
if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2593-
try ais.insertNewline();
2594-
} else {
2595-
try ais.maybeInsertNewline();
2596-
}
2597-
last_line_was_empty = (line.len == 0);
2598-
try ais.writeAll(line);
2599-
}
2600-
}
2601-
2602-
if (i + 1 < section_exprs.len) {
2603-
const next_expr = section_exprs[i + 1];
2604-
const comma = tree.lastToken(expr) + 1;
2505+
// Write trailing comments since they may enable/disable zig fmt
2506+
buf.clearRetainingCapacity();
2507+
var after_expr = tree.lastToken(e);
2508+
after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma);
2509+
try renderSpace(&sub_r, after_expr, tokenSliceForRender(tree, after_expr).len, .none);
26052510

2606-
if (column_counter != row_size - 1) {
2607-
if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2608-
// Neither the current or next expression is multiline
2609-
try renderToken(r, comma, .space); // ,
2610-
assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2611-
const padding = column_widths[column_counter % row_size] - expr_widths[i];
2612-
try ais.splatByteAll(' ', padding);
2511+
buf.clearRetainingCapacity();
2512+
}
2513+
}
26132514

2614-
column_counter += 1;
2615-
continue;
2616-
}
2617-
}
2515+
var remaining_exprs = array_init.ast.elements;
2516+
var remaining_widths = expr_widths;
2517+
while (remaining_exprs.len != 0) {
2518+
var row_size: usize = 1;
2519+
for (1.., remaining_exprs, remaining_widths) |len, e, w| {
2520+
if (w == .nonprint) break;
2521+
row_size = len;
26182522

2619-
if (single_line and row_size != 1) {
2620-
try renderToken(r, comma, .space); // ,
2621-
continue;
2523+
var after_expr = tree.lastToken(e);
2524+
after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma);
2525+
assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace);
2526+
if (!tree.tokensOnSameLine(after_expr, after_expr + 1))
2527+
break;
2528+
} else {
2529+
// All the expressions are on the same line.
2530+
// However, if there is a trailing comma, we put them each on their own line.
2531+
if (tree.tokenTag(rbrace - 1) == .comma)
2532+
row_size = 1;
2533+
}
2534+
2535+
// Determine the size of this section
2536+
const section_end = end: {
2537+
var line_start = row_size; // Start after the first row to ignore comments on it
2538+
break :end for (line_start.., remaining_exprs[line_start..]) |i, e| {
2539+
const expr_first = tree.firstToken(e);
2540+
// Any nonprint character terminates the line because they are always put on their
2541+
// own line, so they will not end up on the same line as the trailing comment.
2542+
if (expr_widths[i - 1] == .nonprint or !tree.tokensOnSameLine(expr_first - 1, expr_first)) {
2543+
line_start = i;
26222544
}
26232545

2624-
column_counter = 0;
2625-
try renderToken(r, comma, .newline); // ,
2626-
try renderExtraNewline(r, next_expr);
2546+
var after_expr = tree.lastToken(e);
2547+
after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma);
2548+
assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace);
2549+
if (hasTrailingComment(tree, after_expr))
2550+
break line_start;
2551+
} else remaining_exprs.len;
2552+
};
2553+
const section_exprs = remaining_exprs[0..section_end];
2554+
const section_widths = remaining_widths[0..section_end];
2555+
remaining_exprs = remaining_exprs[section_end..];
2556+
remaining_widths = remaining_widths[section_end..];
2557+
2558+
// Determine the width of each column
2559+
var col_widths = try gpa.alloc(usize, row_size);
2560+
defer gpa.free(col_widths);
2561+
@memset(col_widths, 0);
2562+
2563+
var col: usize = 0;
2564+
for (section_widths) |w| {
2565+
if (w == .nonprint) {
2566+
col = 0;
2567+
continue;
2568+
}
2569+
col_widths[col] = @max(col_widths[col], @intFromEnum(w));
2570+
col += 1;
2571+
if (col == row_size) {
2572+
col = 0;
26272573
}
26282574
}
26292575

2630-
if (expr_index == array_init.ast.elements.len)
2631-
break;
2576+
// Render each expression
2577+
col = 0;
2578+
for (0.., section_exprs, section_widths) |i, e, w| {
2579+
if (i + 1 == section_end or col + 1 == row_size or
2580+
w == .nonprint or section_widths[i + 1] == .nonprint)
2581+
{
2582+
try renderExpression(r, e, .comma);
2583+
col = 0;
2584+
if (i + 1 != section_end) {
2585+
try renderExtraNewline(r, section_exprs[i + 1]);
2586+
}
2587+
} else {
2588+
try renderExpression(r, e, .comma_space);
2589+
try ais.splatByteAll(' ', col_widths[col] - @intFromEnum(w));
2590+
col += 1;
2591+
}
2592+
}
26322593
}
26332594

2595+
ais.popSpace();
26342596
ais.popIndent();
26352597
return renderToken(r, rbrace, space); // rbrace
26362598
}
@@ -3478,7 +3440,7 @@ fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex)
34783440
const token: Ast.TokenIndex = @intCast(i);
34793441
const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
34803442
const end = tree.tokenStart(token + 1);
3481-
if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
3443+
if (mem.indexOfScalar(u8, tree.source[start..end], '/') != null) return true;
34823444
}
34833445

34843446
return false;
@@ -3688,9 +3650,10 @@ fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) Error
36883650
return lexeme.len;
36893651
}
36903652

3691-
fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3692-
const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3693-
for (between_source) |byte| switch (byte) {
3653+
fn hasTrailingComment(tree: Ast, t: Ast.TokenIndex) bool {
3654+
const start = tree.tokenStart(t) + tree.tokenSlice(t).len;
3655+
const between = tree.source[start..tree.tokenStart(t + 1)];
3656+
for (between) |byte| switch (byte) {
36943657
'\n' => return false,
36953658
'/' => return true,
36963659
else => continue,
@@ -3702,12 +3665,7 @@ fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
37023665
/// start_token and end_token.
37033666
fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
37043667
if (start_token + 1 != end_token) return true;
3705-
const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3706-
for (between_source) |byte| switch (byte) {
3707-
'/' => return true,
3708-
else => continue,
3709-
};
3710-
return false;
3668+
return hasComment(tree, start_token, end_token);
37113669
}
37123670

37133671
fn writeFixingWhitespace(w: *Writer, slice: []const u8) Error!void {
@@ -3794,29 +3752,6 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
37943752
};
37953753
}
37963754

3797-
// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3798-
fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3799-
const first_token = tree.firstToken(exprs[0]);
3800-
if (tree.tokensOnSameLine(first_token, rtoken)) {
3801-
const maybe_comma = rtoken - 1;
3802-
if (tree.tokenTag(maybe_comma) == .comma)
3803-
return 1;
3804-
return exprs.len; // no newlines
3805-
}
3806-
3807-
var count: usize = 1;
3808-
for (exprs, 0..) |expr, i| {
3809-
if (i + 1 < exprs.len) {
3810-
const expr_last_token = tree.lastToken(expr) + 1;
3811-
if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3812-
count += 1;
3813-
} else {
3814-
return count;
3815-
}
3816-
}
3817-
unreachable;
3818-
}
3819-
38203755
/// Automatically inserts indentation of written data by keeping
38213756
/// track of the current indentation level
38223757
///

0 commit comments

Comments
 (0)