From 1b7055b514955f1787937b2ef6097d2e0663da74 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 30 Jan 2023 19:10:03 +0200 Subject: [PATCH] parse and render new for loop syntax --- lib/std/zig/Ast.zig | 117 +++++++++++++++++++--- lib/std/zig/Parse.zig | 193 +++++++++++++++++++++++++----------- lib/std/zig/parser_test.zig | 49 +++++++-- lib/std/zig/render.zig | 156 +++++++++++++++++++++++++---- 4 files changed, 410 insertions(+), 105 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 94cdcff4e7..42eb280966 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -386,6 +386,12 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { .expected_comma_after_switch_prong => { return stream.writeAll("expected ',' after switch prong"); }, + .expected_comma_after_for_operand => { + return stream.writeAll("expected ',' after for operand"); + }, + .expected_comma_after_capture => { + return stream.writeAll("expected ',' after for capture"); + }, .expected_initializer => { return stream.writeAll("expected field initializer"); }, @@ -420,6 +426,12 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { .var_const_decl => { return stream.writeAll("use 'var' or 'const' to declare variable"); }, + .extra_for_capture => { + return stream.writeAll("excess for captures"); + }, + .for_input_not_captured => { + return stream.writeAll("for input is not captured"); + }, .expected_token => { const found_tag = token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)]; @@ -568,6 +580,7 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex { .call, .call_comma, .switch_range, + .for_range, .error_union, => n = datas[n].lhs, @@ -845,6 +858,12 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { .switch_range, => n = datas[n].rhs, + .for_range => if (datas[n].rhs != 0) { + n = datas[n].rhs; + } else { + return main_tokens[n] + end_offset; + }, + .field_access, .unwrap_optional, .grouped_expression, @@ -1263,11 +1282,15 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { assert(extra.else_expr != 0); n = extra.else_expr; }, - .@"if", .@"for" => { + .@"if" => { const extra = tree.extraData(datas[n].rhs, Node.If); assert(extra.else_expr != 0); n = extra.else_expr; }, + .@"for" => { + const extra = @bitCast(Node.For, datas[n].rhs); + n = tree.extra_data[datas[n].lhs + extra.inputs + @boolToInt(extra.has_else)]; + }, .@"suspend" => { if (datas[n].lhs != 0) { n = datas[n].lhs; @@ -1916,26 +1939,28 @@ pub fn whileFull(tree: Ast, node: Node.Index) full.While { }); } -pub fn forSimple(tree: Ast, node: Node.Index) full.While { - const data = tree.nodes.items(.data)[node]; - return tree.fullWhileComponents(.{ - .while_token = tree.nodes.items(.main_token)[node], - .cond_expr = data.lhs, - .cont_expr = 0, +pub fn forSimple(tree: Ast, node: Node.Index) full.For { + const data = &tree.nodes.items(.data)[node]; + const inputs: *[1]Node.Index = &data.lhs; + return tree.fullForComponents(.{ + .for_token = tree.nodes.items(.main_token)[node], + .inputs = inputs[0..1], .then_expr = data.rhs, .else_expr = 0, }); } -pub fn forFull(tree: Ast, node: Node.Index) full.While { +pub fn forFull(tree: Ast, node: Node.Index) full.For { const data = tree.nodes.items(.data)[node]; - const extra = tree.extraData(data.rhs, Node.If); - return tree.fullWhileComponents(.{ - .while_token = tree.nodes.items(.main_token)[node], - .cond_expr = data.lhs, - .cont_expr = 0, - .then_expr = extra.then_expr, - .else_expr = extra.else_expr, + const extra = @bitCast(Node.For, data.rhs); + const inputs = tree.extra_data[data.lhs..][0..extra.inputs]; + const then_expr = tree.extra_data[data.lhs + extra.inputs]; + const else_expr = if (extra.has_else) tree.extra_data[data.lhs + extra.inputs + 1] else 0; + return tree.fullForComponents(.{ + .for_token = tree.nodes.items(.main_token)[node], + .inputs = inputs, + .then_expr = then_expr, + .else_expr = else_expr, }); } @@ -2243,6 +2268,33 @@ fn fullWhileComponents(tree: Ast, info: full.While.Components) full.While { return result; } +fn fullForComponents(tree: Ast, info: full.For.Components) full.For { + const token_tags = tree.tokens.items(.tag); + var result: full.For = .{ + .ast = info, + .inline_token = null, + .label_token = null, + .payload_token = undefined, + .else_token = undefined, + }; + var tok_i = info.for_token - 1; + if (token_tags[tok_i] == .keyword_inline) { + result.inline_token = tok_i; + tok_i -= 1; + } + if (token_tags[tok_i] == .colon and + token_tags[tok_i - 1] == .identifier) + { + result.label_token = tok_i - 1; + } + const last_cond_token = tree.lastToken(info.inputs[info.inputs.len - 1]); + result.payload_token = last_cond_token + 3 + @boolToInt(token_tags[last_cond_token + 1] == .comma); + if (info.else_expr != 0) { + result.else_token = tree.lastToken(info.then_expr) + 1; + } + return result; +} + fn fullCallComponents(tree: Ast, info: full.Call.Components) full.Call { const token_tags = tree.tokens.items(.tag); var result: full.Call = .{ @@ -2279,6 +2331,12 @@ pub fn fullWhile(tree: Ast, node: Node.Index) ?full.While { .while_simple => tree.whileSimple(node), .while_cont => tree.whileCont(node), .@"while" => tree.whileFull(node), + else => null, + }; +} + +pub fn fullFor(tree: Ast, node: Node.Index) ?full.For { + return switch (tree.nodes.items(.tag)[node]) { .for_simple => tree.forSimple(node), .@"for" => tree.forFull(node), else => null, @@ -2453,6 +2511,22 @@ pub const full = struct { }; }; + pub const For = struct { + ast: Components, + inline_token: ?TokenIndex, + label_token: ?TokenIndex, + payload_token: TokenIndex, + /// Populated only if else_expr != 0. + else_token: TokenIndex, + + pub const Components = struct { + for_token: TokenIndex, + inputs: []const Node.Index, + then_expr: Node.Index, + else_expr: Node.Index, + }; + }; + pub const ContainerField = struct { comptime_token: ?TokenIndex, ast: Components, @@ -2795,6 +2869,8 @@ pub const Error = struct { expected_comma_after_param, expected_comma_after_initializer, expected_comma_after_switch_prong, + expected_comma_after_for_operand, + expected_comma_after_capture, expected_initializer, mismatched_binary_op_whitespace, invalid_ampersand_ampersand, @@ -2802,6 +2878,8 @@ pub const Error = struct { expected_var_const, wrong_equal_var_decl, var_const_decl, + extra_for_capture, + for_input_not_captured, zig_style_container, previous_field, @@ -3112,8 +3190,10 @@ pub const Node = struct { @"while", /// `for (lhs) rhs`. for_simple, - /// `for (lhs) a else b`. `if_list[rhs]`. + /// `for (lhs[0..inputs]) lhs[inputs + 1] else lhs[inputs + 2]`. `For[rhs]`. @"for", + /// `lhs..rhs`. + for_range, /// `if (lhs) rhs`. /// `if (lhs) |a| rhs`. if_simple, @@ -3369,6 +3449,11 @@ pub const Node = struct { then_expr: Index, }; + pub const For = packed struct(u32) { + inputs: u31, + has_else: bool, + }; + pub const FnProtoOne = struct { /// Populated if there is exactly 1 parameter. Otherwise there are 0 parameters. param: Index, diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index d498366b34..7ef884d22c 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -104,6 +104,8 @@ fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void { .expected_comma_after_param, .expected_comma_after_initializer, .expected_comma_after_switch_prong, + .expected_comma_after_for_operand, + .expected_comma_after_capture, .expected_semi_or_else, .expected_semi_or_lbrace, .expected_token, @@ -1149,22 +1151,18 @@ fn parseLoopStatement(p: *Parse) !Node.Index { return p.fail(.expected_inlinable); } -/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload -/// /// ForStatement /// <- ForPrefix BlockExpr ( KEYWORD_else Statement )? /// / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement ) fn parseForStatement(p: *Parse) !Node.Index { const for_token = p.eatToken(.keyword_for) orelse return null_node; - _ = try p.expectToken(.l_paren); - const array_expr = try p.expectExpr(); - _ = try p.expectToken(.r_paren); - const found_payload = try p.parsePtrIndexPayload(); - if (found_payload == 0) try p.warn(.expected_loop_payload); - // TODO propose to change the syntax so that semicolons are always required - // inside while statements, even if there is an `else`. + const scratch_top = p.scratch.items.len; + defer p.scratch.shrinkRetainingCapacity(scratch_top); + const inputs = try p.forPrefix(); + var else_required = false; + var seen_semicolon = false; const then_expr = blk: { const block_expr = try p.parseBlockExpr(); if (block_expr != 0) break :blk block_expr; @@ -1173,39 +1171,40 @@ fn parseForStatement(p: *Parse) !Node.Index { return p.fail(.expected_block_or_assignment); } if (p.eatToken(.semicolon)) |_| { - return p.addNode(.{ - .tag = .for_simple, - .main_token = for_token, - .data = .{ - .lhs = array_expr, - .rhs = assign_expr, - }, - }); + seen_semicolon = true; + break :blk assign_expr; } else_required = true; break :blk assign_expr; }; - _ = p.eatToken(.keyword_else) orelse { - if (else_required) { - try p.warn(.expected_semi_or_else); - } + var has_else = false; + if (!seen_semicolon and p.eatToken(.keyword_else) != null) { + try p.scratch.append(p.gpa, then_expr); + const else_stmt = try p.expectStatement(false); + try p.scratch.append(p.gpa, else_stmt); + has_else = true; + } else if (inputs == 1) { + if (else_required) try p.warn(.expected_semi_or_else); return p.addNode(.{ .tag = .for_simple, .main_token = for_token, .data = .{ - .lhs = array_expr, + .lhs = p.scratch.items[scratch_top], .rhs = then_expr, }, }); - }; + } else { + if (else_required) try p.warn(.expected_semi_or_else); + try p.scratch.append(p.gpa, then_expr); + } return p.addNode(.{ .tag = .@"for", .main_token = for_token, .data = .{ - .lhs = array_expr, - .rhs = try p.addExtra(Node.If{ - .then_expr = then_expr, - .else_expr = try p.expectStatement(false), + .lhs = (try p.listToSpan(p.scratch.items[scratch_top..])).start, + .rhs = @bitCast(u32, Node.For{ + .inputs = @intCast(u31, inputs), + .has_else = has_else, }), }, }); @@ -2056,42 +2055,118 @@ fn parseBlock(p: *Parse) !Node.Index { } } -/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload -/// /// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)? fn parseForExpr(p: *Parse) !Node.Index { const for_token = p.eatToken(.keyword_for) orelse return null_node; - _ = try p.expectToken(.l_paren); - const array_expr = try p.expectExpr(); - _ = try p.expectToken(.r_paren); - const found_payload = try p.parsePtrIndexPayload(); - if (found_payload == 0) try p.warn(.expected_loop_payload); + + const scratch_top = p.scratch.items.len; + defer p.scratch.shrinkRetainingCapacity(scratch_top); + const inputs = try p.forPrefix(); const then_expr = try p.expectExpr(); - _ = p.eatToken(.keyword_else) orelse { + var has_else = false; + if (p.eatToken(.keyword_else)) |_| { + try p.scratch.append(p.gpa, then_expr); + const else_expr = try p.expectExpr(); + try p.scratch.append(p.gpa, else_expr); + has_else = true; + } else if (inputs == 1) { return p.addNode(.{ .tag = .for_simple, .main_token = for_token, .data = .{ - .lhs = array_expr, + .lhs = p.scratch.items[scratch_top], .rhs = then_expr, }, }); - }; - const else_expr = try p.expectExpr(); + } else { + try p.scratch.append(p.gpa, then_expr); + } return p.addNode(.{ .tag = .@"for", .main_token = for_token, .data = .{ - .lhs = array_expr, - .rhs = try p.addExtra(Node.If{ - .then_expr = then_expr, - .else_expr = else_expr, + .lhs = (try p.listToSpan(p.scratch.items[scratch_top..])).start, + .rhs = @bitCast(u32, Node.For{ + .inputs = @intCast(u31, inputs), + .has_else = has_else, }), }, }); } +/// ForPrefix <- KEYWORD_for LPAREN ForInput (COMMA ForInput)* COMMA? RPAREN ForPayload +/// +/// ForInput <- Expr (DOT2 Expr?)? +/// +/// ForPayload <- PIPE ASTERISK? IDENTIFIER (COMMA ASTERISK? IDENTIFIER)* PIPE +fn forPrefix(p: *Parse) Error!usize { + const start = p.scratch.items.len; + _ = try p.expectToken(.l_paren); + + while (true) { + var input = try p.expectExpr(); + if (p.eatToken(.ellipsis2)) |ellipsis| { + input = try p.addNode(.{ + .tag = .for_range, + .main_token = ellipsis, + .data = .{ + .lhs = input, + .rhs = try p.parseExpr(), + }, + }); + } + + try p.scratch.append(p.gpa, input); + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, + .r_paren => { + p.tok_i += 1; + break; + }, + .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren), + // Likely just a missing comma; give error but continue parsing. + else => try p.warn(.expected_comma_after_for_operand), + } + if (p.eatToken(.r_paren)) |_| break; + } + const inputs = p.scratch.items.len - start; + + _ = p.eatToken(.pipe) orelse { + try p.warn(.expected_loop_payload); + return inputs; + }; + + var warned_excess = false; + var captures: u32 = 0; + while (true) { + _ = p.eatToken(.asterisk); + const identifier = try p.expectToken(.identifier); + captures += 1; + if (captures > inputs and !warned_excess) { + try p.warnMsg(.{ .tag = .extra_for_capture, .token = identifier }); + warned_excess = true; + } + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, + .pipe => { + p.tok_i += 1; + break; + }, + // Likely just a missing comma; give error but continue parsing. + else => try p.warn(.expected_comma_after_capture), + } + if (p.eatToken(.pipe)) |_| break; + } + + if (captures < inputs) { + const index = p.scratch.items.len - captures; + const input = p.nodes.items(.main_token)[p.scratch.items[index]]; + try p.warnMsg(.{ .tag = .for_input_not_captured, .token = input }); + } + return inputs; +} + /// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? /// /// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? @@ -2752,37 +2827,41 @@ fn expectPrimaryTypeExpr(p: *Parse) !Node.Index { return node; } -/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload -/// /// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)? fn parseForTypeExpr(p: *Parse) !Node.Index { const for_token = p.eatToken(.keyword_for) orelse return null_node; - _ = try p.expectToken(.l_paren); - const array_expr = try p.expectExpr(); - _ = try p.expectToken(.r_paren); - const found_payload = try p.parsePtrIndexPayload(); - if (found_payload == 0) try p.warn(.expected_loop_payload); + + const scratch_top = p.scratch.items.len; + defer p.scratch.shrinkRetainingCapacity(scratch_top); + const inputs = try p.forPrefix(); const then_expr = try p.expectTypeExpr(); - _ = p.eatToken(.keyword_else) orelse { + var has_else = false; + if (p.eatToken(.keyword_else)) |_| { + try p.scratch.append(p.gpa, then_expr); + const else_expr = try p.expectTypeExpr(); + try p.scratch.append(p.gpa, else_expr); + has_else = true; + } else if (inputs == 1) { return p.addNode(.{ .tag = .for_simple, .main_token = for_token, .data = .{ - .lhs = array_expr, + .lhs = p.scratch.items[scratch_top], .rhs = then_expr, }, }); - }; - const else_expr = try p.expectTypeExpr(); + } else { + try p.scratch.append(p.gpa, then_expr); + } return p.addNode(.{ .tag = .@"for", .main_token = for_token, .data = .{ - .lhs = array_expr, - .rhs = try p.addExtra(Node.If{ - .then_expr = then_expr, - .else_expr = else_expr, + .lhs = (try p.listToSpan(p.scratch.items[scratch_top..])).start, + .rhs = @bitCast(u32, Node.For{ + .inputs = @intCast(u31, inputs), + .has_else = has_else, }), }, }); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 3c44322ccc..d24dedfeff 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3457,11 +3457,11 @@ test "zig fmt: for" { \\ for (a) |*v| \\ continue; \\ - \\ for (a) |v, i| { + \\ for (a, 0..) |v, i| { \\ continue; \\ } \\ - \\ for (a) |v, i| + \\ for (a, 0..) |v, i| \\ continue; \\ \\ for (a) |b| switch (b) { @@ -3469,17 +3469,24 @@ test "zig fmt: for" { \\ d => {}, \\ }; \\ - \\ const res = for (a) |v, i| { + \\ const res = for (a, 0..) |v, i| { \\ break v; \\ } else { \\ unreachable; \\ }; \\ \\ var num: usize = 0; - \\ inline for (a) |v, i| { + \\ inline for (a, 0..1) |v, i| { \\ num += v; \\ num += i; \\ } + \\ + \\ for (a, b) | + \\ long_name, + \\ another_long_name, + \\ | { + \\ continue; + \\ } \\} \\ ); @@ -3499,6 +3506,26 @@ test "zig fmt: for" { \\} \\ ); + + try testTransform( + \\test "fix for" { + \\ for (a, b, c,) |long, another, third,| {} + \\} + \\ + , + \\test "fix for" { + \\ for ( + \\ a, + \\ b, + \\ c, + \\ ) | + \\ long, + \\ another, + \\ third, + \\ | {} + \\} + \\ + ); } test "zig fmt: for if" { @@ -4358,7 +4385,7 @@ test "zig fmt: hex literals with underscore separators" { try testTransform( \\pub fn orMask(a: [ 1_000 ]u64, b: [ 1_000] u64) [1_000]u64 { \\ var c: [1_000]u64 = [1]u64{ 0xFFFF_FFFF_FFFF_FFFF}**1_000; - \\ for (c [ 1_0 .. ]) |_, i| { + \\ for (c [ 1_0 .. ], 0..) |_, i| { \\ c[i] = (a[i] | b[i]) & 0xCCAA_CCAA_CCAA_CCAA; \\ } \\ return c; @@ -4368,7 +4395,7 @@ test "zig fmt: hex literals with underscore separators" { , \\pub fn orMask(a: [1_000]u64, b: [1_000]u64) [1_000]u64 { \\ var c: [1_000]u64 = [1]u64{0xFFFF_FFFF_FFFF_FFFF} ** 1_000; - \\ for (c[1_0..]) |_, i| { + \\ for (c[1_0..], 0..) |_, i| { \\ c[i] = (a[i] | b[i]) & 0xCCAA_CCAA_CCAA_CCAA; \\ } \\ return c; @@ -4880,10 +4907,10 @@ test "zig fmt: remove trailing whitespace after doc comment" { test "zig fmt: for loop with ptr payload and index" { try testCanonical( \\test { - \\ for (self.entries.items) |*item, i| {} - \\ for (self.entries.items) |*item, i| + \\ for (self.entries.items, 0..) |*item, i| {} + \\ for (self.entries.items, 0..) |*item, i| \\ a = b; - \\ for (self.entries.items) |*item, i| a = b; + \\ for (self.entries.items, 0..) |*item, i| a = b; \\} \\ ); @@ -5471,7 +5498,7 @@ test "zig fmt: canonicalize symbols (primitive types)" { \\ _ = @"void": { \\ break :@"void"; \\ }; - \\ for ("hi") |@"u3", @"i4"| { + \\ for ("hi", 0..) |@"u3", @"i4"| { \\ _ = @"u3"; \\ _ = @"i4"; \\ } @@ -5523,7 +5550,7 @@ test "zig fmt: canonicalize symbols (primitive types)" { \\ _ = void: { \\ break :void; \\ }; - \\ for ("hi") |@"u3", @"i4"| { + \\ for ("hi", 0..) |@"u3", @"i4"| { \\ _ = @"u3"; \\ _ = @"i4"; \\ } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 97bc85efac..0e8d3125ac 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -353,6 +353,16 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, try renderToken(ais, tree, main_tokens[node], .none); return renderExpression(gpa, ais, tree, infix.rhs, space); }, + .for_range => { + const infix = datas[node]; + try renderExpression(gpa, ais, tree, infix.lhs, .none); + if (infix.rhs != 0) { + try renderToken(ais, tree, main_tokens[node], .none); + return renderExpression(gpa, ais, tree, infix.rhs, space); + } else { + return renderToken(ais, tree, main_tokens[node], space); + } + }, .add, .add_wrap, @@ -694,9 +704,11 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, .while_simple, .while_cont, .@"while", + => return renderWhile(gpa, ais, tree, tree.fullWhile(node).?, space), + .for_simple, .@"for", - => return renderWhile(gpa, ais, tree, tree.fullWhile(node).?, space), + => return renderFor(gpa, ais, tree, tree.fullFor(node).?, space), .if_simple, .@"if", @@ -1054,10 +1066,9 @@ fn renderIf(gpa: Allocator, ais: *Ais, tree: Ast, if_node: Ast.full.If, space: S }, space); } -/// Note that this function is additionally used to render if and for expressions, with +/// Note that this function is additionally used to render if expressions, with /// respective values set to null. fn renderWhile(gpa: Allocator, ais: *Ais, tree: Ast, while_node: Ast.full.While, space: Space) Error!void { - const node_tags = tree.nodes.items(.tag); const token_tags = tree.tokens.items(.tag); if (while_node.label_token) |label| { @@ -1108,9 +1119,34 @@ fn renderWhile(gpa: Allocator, ais: *Ais, tree: Ast, while_node: Ast.full.While, last_prefix_token = tree.lastToken(while_node.ast.cont_expr) + 1; // rparen } - const then_expr_is_block = nodeIsBlock(node_tags[while_node.ast.then_expr]); + try renderThenElse( + gpa, + ais, + tree, + last_prefix_token, + while_node.ast.then_expr, + while_node.else_token, + while_node.error_token, + while_node.ast.else_expr, + space, + ); +} + +fn renderThenElse( + gpa: Allocator, + ais: *Ais, + tree: Ast, + last_prefix_token: Ast.TokenIndex, + then_expr: Ast.Node.Index, + else_token: Ast.TokenIndex, + maybe_error_token: ?Ast.TokenIndex, + else_expr: Ast.Node.Index, + space: Space, +) Error!void { + const node_tags = tree.nodes.items(.tag); + const then_expr_is_block = nodeIsBlock(node_tags[then_expr]); const indent_then_expr = !then_expr_is_block and - !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(while_node.ast.then_expr)); + !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr)); if (indent_then_expr or (then_expr_is_block and ais.isLineOverIndented())) { ais.pushIndentNextLine(); try renderToken(ais, tree, last_prefix_token, .newline); @@ -1119,45 +1155,115 @@ fn renderWhile(gpa: Allocator, ais: *Ais, tree: Ast, while_node: Ast.full.While, try renderToken(ais, tree, last_prefix_token, .space); } - if (while_node.ast.else_expr != 0) { + if (else_expr != 0) { if (indent_then_expr) { ais.pushIndent(); - try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .newline); + try renderExpression(gpa, ais, tree, then_expr, .newline); ais.popIndent(); } else { - try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .space); + try renderExpression(gpa, ais, tree, then_expr, .space); } - var last_else_token = while_node.else_token; + var last_else_token = else_token; - if (while_node.error_token) |error_token| { - try renderToken(ais, tree, while_node.else_token, .space); // else + if (maybe_error_token) |error_token| { + try renderToken(ais, tree, else_token, .space); // else try renderToken(ais, tree, error_token - 1, .none); // | try renderIdentifier(ais, tree, error_token, .none, .preserve_when_shadowing); // identifier last_else_token = error_token + 1; // | } const indent_else_expr = indent_then_expr and - !nodeIsBlock(node_tags[while_node.ast.else_expr]) and - !nodeIsIfForWhileSwitch(node_tags[while_node.ast.else_expr]); + !nodeIsBlock(node_tags[else_expr]) and + !nodeIsIfForWhileSwitch(node_tags[else_expr]); if (indent_else_expr) { ais.pushIndentNextLine(); try renderToken(ais, tree, last_else_token, .newline); ais.popIndent(); - try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space); + try renderExpressionIndented(gpa, ais, tree, else_expr, space); } else { try renderToken(ais, tree, last_else_token, .space); - try renderExpression(gpa, ais, tree, while_node.ast.else_expr, space); + try renderExpression(gpa, ais, tree, else_expr, space); } } else { if (indent_then_expr) { - try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space); + try renderExpressionIndented(gpa, ais, tree, then_expr, space); } else { - try renderExpression(gpa, ais, tree, while_node.ast.then_expr, space); + try renderExpression(gpa, ais, tree, then_expr, space); } } } +fn renderFor(gpa: Allocator, ais: *Ais, tree: Ast, for_node: Ast.full.For, space: Space) Error!void { + const token_tags = tree.tokens.items(.tag); + + if (for_node.label_token) |label| { + try renderIdentifier(ais, tree, label, .none, .eagerly_unquote); // label + try renderToken(ais, tree, label + 1, .space); // : + } + + if (for_node.inline_token) |inline_token| { + try renderToken(ais, tree, inline_token, .space); // inline + } + + try renderToken(ais, tree, for_node.ast.for_token, .space); // if/for/while + + const lparen = for_node.ast.for_token + 1; + try renderParamList(gpa, ais, tree, lparen, for_node.ast.inputs, .space); + + var cur = for_node.payload_token; + const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?; + if (token_tags[pipe - 1] == .comma) { + ais.pushIndentNextLine(); + try renderToken(ais, tree, cur - 1, .newline); // | + while (true) { + if (token_tags[cur] == .asterisk) { + try renderToken(ais, tree, cur, .none); // * + cur += 1; + } + try renderIdentifier(ais, tree, cur, .none, .preserve_when_shadowing); // identifier + cur += 1; + if (token_tags[cur] == .comma) { + try renderToken(ais, tree, cur, .newline); // , + cur += 1; + } + if (token_tags[cur] == .pipe) { + break; + } + } + ais.popIndent(); + } else { + try renderToken(ais, tree, cur - 1, .none); // | + while (true) { + if (token_tags[cur] == .asterisk) { + try renderToken(ais, tree, cur, .none); // * + cur += 1; + } + try renderIdentifier(ais, tree, cur, .none, .preserve_when_shadowing); // identifier + cur += 1; + if (token_tags[cur] == .comma) { + try renderToken(ais, tree, cur, .space); // , + cur += 1; + } + if (token_tags[cur] == .pipe) { + break; + } + } + } + + try renderThenElse( + gpa, + ais, + tree, + cur, + for_node.ast.then_expr, + for_node.else_token, + null, + for_node.ast.else_expr, + space, + ); +} + fn renderContainerField( gpa: Allocator, ais: *Ais, @@ -2206,15 +2312,23 @@ fn renderCall( call: Ast.full.Call, space: Space, ) Error!void { - const token_tags = tree.tokens.items(.tag); - if (call.async_token) |async_token| { try renderToken(ais, tree, async_token, .space); } try renderExpression(gpa, ais, tree, call.ast.fn_expr, .none); + try renderParamList(gpa, ais, tree, call.ast.lparen, call.ast.params, space); +} + +fn renderParamList( + gpa: Allocator, + ais: *Ais, + tree: Ast, + lparen: Ast.TokenIndex, + params: []const Ast.Node.Index, + space: Space, +) Error!void { + const token_tags = tree.tokens.items(.tag); - const lparen = call.ast.lparen; - const params = call.ast.params; if (params.len == 0) { ais.pushIndentNextLine(); try renderToken(ais, tree, lparen, .none);