Skip to content

Commit 602ba9d

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 70c335b commit 602ba9d

File tree

2 files changed

+234
-188
lines changed

2 files changed

+234
-188
lines changed

lib/std/zig/Ast/Render.zig

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

24272427
try ais.pushIndent(.normal);
24282428
try renderToken(r, array_init.ast.lbrace, .newline);
2429-
2430-
var expr_index: usize = 0;
2431-
while (true) {
2432-
const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace);
2433-
const row_exprs = array_init.ast.elements[expr_index..];
2434-
// A place to store the width of each expression and its column's maximum
2435-
const widths = try gpa.alloc(usize, row_exprs.len + row_size);
2436-
defer gpa.free(widths);
2437-
@memset(widths, 0);
2438-
2439-
const expr_newlines = try gpa.alloc(bool, row_exprs.len);
2440-
defer gpa.free(expr_newlines);
2441-
@memset(expr_newlines, false);
2442-
2443-
const expr_widths = widths[0..row_exprs.len];
2444-
const column_widths = widths[row_exprs.len..];
2445-
2446-
// Find next row with trailing comment (if any) to end the current section.
2447-
const section_end = sec_end: {
2448-
var this_line_first_expr: usize = 0;
2449-
var this_line_size = rowSize(tree, row_exprs, rbrace);
2450-
for (row_exprs, 0..) |expr, i| {
2451-
// Ignore comment on first line of this section.
2452-
if (i == 0) continue;
2453-
const expr_last_token = tree.lastToken(expr);
2454-
if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token))
2455-
continue;
2456-
// Track start of line containing comment.
2457-
if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) {
2458-
this_line_first_expr = i;
2459-
this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace);
2460-
}
2461-
2462-
const maybe_comma = expr_last_token + 1;
2463-
if (tree.tokenTag(maybe_comma) == .comma) {
2464-
if (hasSameLineComment(tree, maybe_comma))
2465-
break :sec_end i - this_line_size + 1;
2466-
}
2467-
}
2468-
break :sec_end row_exprs.len;
2469-
};
2470-
expr_index += section_end;
2471-
2472-
const section_exprs = row_exprs[0..section_end];
2473-
2474-
var sub_expr_buffer: std.io.Writer.Allocating = .init(gpa);
2475-
defer sub_expr_buffer.deinit();
2476-
2477-
const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1);
2478-
defer gpa.free(sub_expr_buffer_starts);
2479-
2480-
var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.writer, indent_delta);
2481-
defer auto_indenting_stream.deinit();
2482-
var sub_render: Render = .{
2429+
try ais.pushSpace(.comma);
2430+
2431+
const expr_widths = try gpa.alloc(enum(usize) {
2432+
/// The expression contains non-printable characters (e.g. unicode / newlines)
2433+
/// or has formatting disabled at the start or end.
2434+
nonprint = std.math.maxInt(usize),
2435+
_,
2436+
}, array_init.ast.elements.len);
2437+
defer gpa.free(expr_widths);
2438+
{
2439+
var buf: std.Io.Writer.Allocating = .init(gpa);
2440+
defer buf.deinit();
2441+
var sub_ais: AutoIndentingStream = .init(gpa, &buf.writer, indent_delta);
2442+
sub_ais.disabled_offset = ais.disabled_offset;
2443+
defer sub_ais.deinit();
2444+
var sub_r: Render = .{
24832445
.gpa = r.gpa,
2484-
.ais = &auto_indenting_stream,
2446+
.ais = &sub_ais,
24852447
.tree = r.tree,
24862448
.fixups = r.fixups,
24872449
};
2488-
2489-
// Calculate size of columns in current section
2490-
var column_counter: usize = 0;
2491-
var single_line = true;
2492-
var contains_newline = false;
2493-
for (section_exprs, 0..) |expr, i| {
2494-
const start = sub_expr_buffer.getWritten().len;
2495-
sub_expr_buffer_starts[i] = start;
2496-
2497-
if (i + 1 < section_exprs.len) {
2498-
try renderExpression(&sub_render, expr, .none);
2499-
const written = sub_expr_buffer.getWritten();
2500-
const width = written.len - start;
2501-
const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null;
2502-
contains_newline = contains_newline or this_contains_newline;
2503-
expr_widths[i] = width;
2504-
expr_newlines[i] = this_contains_newline;
2505-
2506-
if (!this_contains_newline) {
2507-
const column = column_counter % row_size;
2508-
column_widths[column] = @max(column_widths[column], width);
2509-
2510-
const expr_last_token = tree.lastToken(expr) + 1;
2511-
const next_expr = section_exprs[i + 1];
2512-
column_counter += 1;
2513-
if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false;
2514-
} else {
2515-
single_line = false;
2516-
column_counter = 0;
2517-
}
2450+
for (array_init.ast.elements, expr_widths) |e, *width| {
2451+
const begin_disabled = sub_ais.disabled_offset != null;
2452+
// `.skip` space so trailing commments aren't included
2453+
try renderExpressionComma(&sub_r, e, .skip);
2454+
if (!begin_disabled and sub_ais.disabled_offset == null) {
2455+
const w = buf.getWritten();
2456+
width.* = for (w) |c| {
2457+
if (!std.ascii.isPrint(c))
2458+
break .nonprint;
2459+
} else @enumFromInt(w.len - @intFromBool(w[w.len - 1] == ','));
25182460
} else {
2519-
try ais.pushSpace(.comma);
2520-
try renderExpression(&sub_render, expr, .comma);
2521-
ais.popSpace();
2522-
2523-
const written = sub_expr_buffer.getWritten();
2524-
const width = written.len - start - 2;
2525-
const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null;
2526-
contains_newline = contains_newline or this_contains_newline;
2527-
expr_widths[i] = width;
2528-
expr_newlines[i] = contains_newline;
2529-
2530-
if (!contains_newline) {
2531-
const column = column_counter % row_size;
2532-
column_widths[column] = @max(column_widths[column], width);
2533-
}
2461+
width.* = .nonprint;
25342462
}
2535-
}
2536-
sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.getWritten().len;
25372463

2538-
// Render exprs in current section.
2539-
column_counter = 0;
2540-
for (section_exprs, 0..) |expr, i| {
2541-
const start = sub_expr_buffer_starts[i];
2542-
const end = sub_expr_buffer_starts[i + 1];
2543-
const expr_text = sub_expr_buffer.getWritten()[start..end];
2544-
if (!expr_newlines[i]) {
2545-
try ais.writeAll(expr_text);
2546-
} else {
2547-
var by_line = std.mem.splitScalar(u8, expr_text, '\n');
2548-
var last_line_was_empty = false;
2549-
try ais.writeAll(by_line.first());
2550-
while (by_line.next()) |line| {
2551-
if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) {
2552-
try ais.insertNewline();
2553-
} else {
2554-
try ais.maybeInsertNewline();
2555-
}
2556-
last_line_was_empty = (line.len == 0);
2557-
try ais.writeAll(line);
2558-
}
2559-
}
2560-
2561-
if (i + 1 < section_exprs.len) {
2562-
const next_expr = section_exprs[i + 1];
2563-
const comma = tree.lastToken(expr) + 1;
2464+
// Write trailing comments since they may enable/disable zig fmt
2465+
buf.clearRetainingCapacity();
2466+
var after_expr = tree.lastToken(e);
2467+
after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma);
2468+
try renderSpace(&sub_r, after_expr, tokenSliceForRender(tree, after_expr).len, .none);
25642469

2565-
if (column_counter != row_size - 1) {
2566-
if (!expr_newlines[i] and !expr_newlines[i + 1]) {
2567-
// Neither the current or next expression is multiline
2568-
try renderToken(r, comma, .space); // ,
2569-
assert(column_widths[column_counter % row_size] >= expr_widths[i]);
2570-
const padding = column_widths[column_counter % row_size] - expr_widths[i];
2571-
try ais.splatByteAll(' ', padding);
2470+
buf.clearRetainingCapacity();
2471+
}
2472+
}
25722473

2573-
column_counter += 1;
2574-
continue;
2575-
}
2576-
}
2474+
var remaining_exprs = array_init.ast.elements;
2475+
var remaining_widths = expr_widths;
2476+
while (remaining_exprs.len != 0) {
2477+
var row_size: usize = 1;
2478+
for (1.., remaining_exprs, remaining_widths) |len, e, w| {
2479+
if (w == .nonprint) break;
2480+
row_size = len;
25772481

2578-
if (single_line and row_size != 1) {
2579-
try renderToken(r, comma, .space); // ,
2580-
continue;
2482+
var after_expr = tree.lastToken(e);
2483+
after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma);
2484+
assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace);
2485+
if (!tree.tokensOnSameLine(after_expr, after_expr + 1))
2486+
break;
2487+
} else {
2488+
// All the expressions are on the same line.
2489+
// However, if there is a trailing comma, we put them each on their own line.
2490+
if (tree.tokenTag(rbrace - 1) == .comma)
2491+
row_size = 1;
2492+
}
2493+
2494+
// Determine the size of this section
2495+
const section_end = end: {
2496+
var line_start = row_size; // Start after the first row to ignore comments on it
2497+
break :end for (line_start.., remaining_exprs[line_start..]) |i, e| {
2498+
const expr_first = tree.firstToken(e);
2499+
// Any nonprint character terminates the line because they are always put on their
2500+
// own line, so they will not end up on the same line as the trailing comment.
2501+
if (expr_widths[i - 1] == .nonprint or !tree.tokensOnSameLine(expr_first - 1, expr_first)) {
2502+
line_start = i;
25812503
}
25822504

2583-
column_counter = 0;
2584-
try renderToken(r, comma, .newline); // ,
2585-
try renderExtraNewline(r, next_expr);
2505+
var after_expr = tree.lastToken(e);
2506+
after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma);
2507+
assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace);
2508+
if (hasTrailingComment(tree, after_expr))
2509+
break line_start;
2510+
} else remaining_exprs.len;
2511+
};
2512+
const section_exprs = remaining_exprs[0..section_end];
2513+
const section_widths = remaining_widths[0..section_end];
2514+
remaining_exprs = remaining_exprs[section_end..];
2515+
remaining_widths = remaining_widths[section_end..];
2516+
2517+
// Determine the width of each column
2518+
var col_widths = try gpa.alloc(usize, row_size);
2519+
defer gpa.free(col_widths);
2520+
@memset(col_widths, 0);
2521+
2522+
var col: usize = 0;
2523+
for (section_widths) |w| {
2524+
if (w == .nonprint) {
2525+
col = 0;
2526+
continue;
2527+
}
2528+
col_widths[col] = @max(col_widths[col], @intFromEnum(w));
2529+
col += 1;
2530+
if (col == row_size) {
2531+
col = 0;
25862532
}
25872533
}
25882534

2589-
if (expr_index == array_init.ast.elements.len)
2590-
break;
2535+
// Render each expression
2536+
col = 0;
2537+
for (0.., section_exprs, section_widths) |i, e, w| {
2538+
if (i + 1 == section_end or col + 1 == row_size or
2539+
w == .nonprint or section_widths[i + 1] == .nonprint)
2540+
{
2541+
try renderExpression(r, e, .comma);
2542+
col = 0;
2543+
if (i + 1 != section_end) {
2544+
try renderExtraNewline(r, section_exprs[i + 1]);
2545+
}
2546+
} else {
2547+
try renderExpression(r, e, .comma_space);
2548+
try ais.splatByteAll(' ', col_widths[col] - @intFromEnum(w));
2549+
col += 1;
2550+
}
2551+
}
25912552
}
25922553

2554+
ais.popSpace();
25932555
ais.popIndent();
25942556
return renderToken(r, rbrace, space); // rbrace
25952557
}
@@ -3437,7 +3399,7 @@ fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex)
34373399
const token: Ast.TokenIndex = @intCast(i);
34383400
const start = tree.tokenStart(token) + tree.tokenSlice(token).len;
34393401
const end = tree.tokenStart(token + 1);
3440-
if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true;
3402+
if (mem.indexOfScalar(u8, tree.source[start..end], '/') != null) return true;
34413403
}
34423404

34433405
return false;
@@ -3647,9 +3609,10 @@ fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) Error
36473609
return lexeme.len;
36483610
}
36493611

3650-
fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
3651-
const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)];
3652-
for (between_source) |byte| switch (byte) {
3612+
fn hasTrailingComment(tree: Ast, t: Ast.TokenIndex) bool {
3613+
const start = tree.tokenStart(t) + tree.tokenSlice(t).len;
3614+
const between = tree.source[start..tree.tokenStart(t + 1)];
3615+
for (between) |byte| switch (byte) {
36533616
'\n' => return false,
36543617
'/' => return true,
36553618
else => continue,
@@ -3661,12 +3624,7 @@ fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool {
36613624
/// start_token and end_token.
36623625
fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool {
36633626
if (start_token + 1 != end_token) return true;
3664-
const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)];
3665-
for (between_source) |byte| switch (byte) {
3666-
'/' => return true,
3667-
else => continue,
3668-
};
3669-
return false;
3627+
return hasComment(tree, start_token, end_token);
36703628
}
36713629

36723630
fn writeFixingWhitespace(w: *Writer, slice: []const u8) Error!void {
@@ -3753,29 +3711,6 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool {
37533711
};
37543712
}
37553713

3756-
// Returns the number of nodes in `exprs` that are on the same line as `rtoken`.
3757-
fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize {
3758-
const first_token = tree.firstToken(exprs[0]);
3759-
if (tree.tokensOnSameLine(first_token, rtoken)) {
3760-
const maybe_comma = rtoken - 1;
3761-
if (tree.tokenTag(maybe_comma) == .comma)
3762-
return 1;
3763-
return exprs.len; // no newlines
3764-
}
3765-
3766-
var count: usize = 1;
3767-
for (exprs, 0..) |expr, i| {
3768-
if (i + 1 < exprs.len) {
3769-
const expr_last_token = tree.lastToken(expr) + 1;
3770-
if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count;
3771-
count += 1;
3772-
} else {
3773-
return count;
3774-
}
3775-
}
3776-
unreachable;
3777-
}
3778-
37793714
/// Automatically inserts indentation of written data by keeping
37803715
/// track of the current indentation level
37813716
///

0 commit comments

Comments
 (0)