zig fmt: implement switches

This commit is contained in:
Isaac Freund
2021-02-08 13:38:24 +01:00
committed by Andrew Kelley
parent 837cd748a8
commit d869133a9f
4 changed files with 247 additions and 260 deletions

View File

@@ -213,6 +213,7 @@ pub const Tree = struct {
.Await,
.OptionalType,
.Switch,
.SwitchComma,
.IfSimple,
.If,
.Suspend,
@@ -313,7 +314,6 @@ pub const Tree = struct {
.StructInit,
.CallOne,
.Call,
.SwitchCaseOne,
.SwitchRange,
.FnDecl,
.ErrorUnion,
@@ -406,7 +406,19 @@ pub const Tree = struct {
};
},
.SwitchCaseMulti => unreachable, // TODO
.SwitchCaseOne => {
if (datas[n].lhs == 0) {
return main_tokens[n] - 1; // else token
} else {
n = datas[n].lhs;
}
},
.SwitchCase => {
const extra = tree.extraData(datas[n].lhs, Node.SubRange);
assert(extra.end - extra.start > 0);
n = extra.start;
},
.WhileSimple => unreachable, // TODO
.WhileCont => unreachable, // TODO
.While => unreachable, // TODO
@@ -494,6 +506,8 @@ pub const Tree = struct {
.PtrTypeSentinel,
.PtrType,
.PtrTypeBitRange,
.SwitchCaseOne,
.SwitchCase,
=> n = datas[n].rhs,
.FieldAccess,
@@ -532,6 +546,16 @@ pub const Tree = struct {
}
n = tree.extra_data[params.end - 1]; // last parameter
},
.Switch => {
const cases = tree.extraData(datas[n].rhs, Node.SubRange);
if (cases.end - cases.start == 0) {
end_offset += 3; // rparen, lbrace, rbrace
n = datas[n].lhs; // condition expression
} else {
end_offset += 1; // for the rbrace
n = tree.extra_data[cases.end - 1]; // last case
}
},
.ContainerDeclArg => {
const members = tree.extraData(datas[n].rhs, Node.SubRange);
if (members.end - members.start == 0) {
@@ -542,7 +566,9 @@ pub const Tree = struct {
n = tree.extra_data[members.end - 1]; // last parameter
}
},
.ContainerDeclArgComma => {
.ContainerDeclArgComma,
.SwitchComma,
=> {
const members = tree.extraData(datas[n].rhs, Node.SubRange);
assert(members.end - members.start > 0);
end_offset += 2; // for the comma + rbrace
@@ -737,16 +763,13 @@ pub const Tree = struct {
.TaggedUnionEnumTag => unreachable, // TODO
.TaggedUnionEnumTagComma => unreachable, // TODO
.Switch => unreachable, // TODO
.If => unreachable, // TODO
.Continue => unreachable, // TODO
.AsmSimple => unreachable, // TODO
.Asm => unreachable, // TODO
.SwitchCaseOne => unreachable, // TODO
.SwitchRange => unreachable, // TODO
.ArrayType => unreachable, // TODO
.ArrayTypeSentinel => unreachable, // TODO
.SwitchCaseMulti => unreachable, // TODO
.WhileCont => unreachable, // TODO
.While => unreachable, // TODO
.ForSimple => unreachable, // TODO
@@ -1202,7 +1225,8 @@ pub const Tree = struct {
}
pub fn containerDeclArg(tree: Tree, node: Node.Index) Full.ContainerDecl {
assert(tree.nodes.items(.tag)[node] == .ContainerDeclArg);
assert(tree.nodes.items(.tag)[node] == .ContainerDeclArg or
tree.nodes.items(.tag)[node] == .ContainerDeclArgComma);
const data = tree.nodes.items(.data)[node];
const members_range = tree.extraData(data.rhs, Node.SubRange);
return tree.fullContainerDecl(.{
@@ -1214,7 +1238,8 @@ pub const Tree = struct {
}
pub fn taggedUnionTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) Full.ContainerDecl {
assert(tree.nodes.items(.tag)[node] == .TaggedUnionTwo);
assert(tree.nodes.items(.tag)[node] == .TaggedUnionTwo or
tree.nodes.items(.tag)[node] == .TaggedUnionTwoComma);
const data = tree.nodes.items(.data)[node];
buffer.* = .{ data.lhs, data.rhs };
const members = if (data.rhs != 0)
@@ -1233,7 +1258,8 @@ pub const Tree = struct {
}
pub fn taggedUnion(tree: Tree, node: Node.Index) Full.ContainerDecl {
assert(tree.nodes.items(.tag)[node] == .TaggedUnion);
assert(tree.nodes.items(.tag)[node] == .TaggedUnion or
tree.nodes.items(.tag)[node] == .TaggedUnionComma);
const data = tree.nodes.items(.data)[node];
const main_token = tree.nodes.items(.main_token)[node];
return tree.fullContainerDecl(.{
@@ -1245,7 +1271,8 @@ pub const Tree = struct {
}
pub fn taggedUnionEnumTag(tree: Tree, node: Node.Index) Full.ContainerDecl {
assert(tree.nodes.items(.tag)[node] == .TaggedUnionEnumTag);
assert(tree.nodes.items(.tag)[node] == .TaggedUnionEnumTag or
tree.nodes.items(.tag)[node] == .TaggedUnionEnumTagComma);
const data = tree.nodes.items(.data)[node];
const members_range = tree.extraData(data.rhs, Node.SubRange);
const main_token = tree.nodes.items(.main_token)[node];
@@ -1257,6 +1284,25 @@ pub const Tree = struct {
});
}
pub fn switchCaseOne(tree: Tree, node: Node.Index) Full.SwitchCase {
const data = &tree.nodes.items(.data)[node];
return tree.fullSwitchCase(.{
.values = if (data.lhs == 0) &.{} else @ptrCast([*]Node.Index, &data.lhs)[0..1],
.arrow_token = tree.nodes.items(.main_token)[node],
.target_expr = data.rhs,
});
}
pub fn switchCase(tree: Tree, node: Node.Index) Full.SwitchCase {
const data = tree.nodes.items(.data)[node];
const extra = tree.extraData(data.lhs, Node.SubRange);
return tree.fullSwitchCase(.{
.values = tree.extra_data[extra.start..extra.end],
.arrow_token = tree.nodes.items(.main_token)[node],
.target_expr = data.rhs,
});
}
fn fullVarDecl(tree: Tree, info: Full.VarDecl.Ast) Full.VarDecl {
const token_tags = tree.tokens.items(.tag);
var result: Full.VarDecl = .{
@@ -1407,6 +1453,18 @@ pub const Tree = struct {
}
return result;
}
fn fullSwitchCase(tree: Tree, info: Full.SwitchCase.Ast) Full.SwitchCase {
const token_tags = tree.tokens.items(.tag);
var result: Full.SwitchCase = .{
.ast = info,
.payload_token = null,
};
if (token_tags[info.arrow_token + 1] == .Pipe) {
result.payload_token = info.arrow_token + 2;
}
return result;
}
};
/// Fully assembled AST node information.
@@ -1552,6 +1610,20 @@ pub const Full = struct {
arg: Node.Index,
};
};
pub const SwitchCase = struct {
/// Points to the first token after the `|`. Will either be an identifier or
/// a `*` (with an identifier immediately after it).
payload_token: ?TokenIndex,
ast: Ast,
pub const Ast = struct {
/// If empty, this is an else case
values: []const Node.Index,
arrow_token: TokenIndex,
target_expr: Node.Index,
};
};
};
pub const Error = union(enum) {
@@ -1996,13 +2068,16 @@ pub const Node = struct {
/// `lhs(a, b, c)`. `sub_range_list[rhs]`.
/// main_token is the `(`.
Call,
/// `switch(lhs) {}`. `sub_range_list[rhs]`.
/// `switch(lhs) {}`. `SubRange[rhs]`.
Switch,
/// Same as Switch except there is known to be a trailing comma
/// before the final rbrace
SwitchComma,
/// `lhs => rhs`. If lhs is omitted it means `else`.
/// main_token is the `=>`
SwitchCaseOne,
/// `a, b, c => rhs`. `sub_range_list[lhs]`.
SwitchCaseMulti,
/// `a, b, c => rhs`. `SubRange[lhs]`.
SwitchCase,
/// `lhs...rhs`.
SwitchRange,
/// `while (lhs) rhs`.

View File

@@ -2887,10 +2887,11 @@ const Parser = struct {
_ = try p.expectToken(.RParen);
_ = try p.expectToken(.LBrace);
const cases = try p.parseSwitchProngList();
const trailing_comma = p.token_tags[p.tok_i - 1] == .Comma;
_ = try p.expectToken(.RBrace);
return p.addNode(.{
.tag = .Switch,
.tag = if (trailing_comma) .SwitchComma else .Switch,
.main_token = switch_token,
.data = .{
.lhs = expr_node,
@@ -3208,7 +3209,7 @@ const Parser = struct {
const arrow_token = try p.expectToken(.EqualAngleBracketRight);
_ = try p.parsePtrPayload();
return p.addNode(.{
.tag = .SwitchCaseMulti,
.tag = .SwitchCase,
.main_token = arrow_token,
.data = .{
.lhs = try p.addExtra(Node.SubRange{

View File

@@ -1671,32 +1671,32 @@ test "zig fmt: block in slice expression" {
// \\
// );
//}
//
//test "zig fmt: switch cases trailing comma" {
// try testTransform(
// \\fn switch_cases(x: i32) void {
// \\ switch (x) {
// \\ 1,2,3 => {},
// \\ 4,5, => {},
// \\ 6... 8, => {},
// \\ else => {},
// \\ }
// \\}
// ,
// \\fn switch_cases(x: i32) void {
// \\ switch (x) {
// \\ 1, 2, 3 => {},
// \\ 4,
// \\ 5,
// \\ => {},
// \\ 6...8 => {},
// \\ else => {},
// \\ }
// \\}
// \\
// );
//}
//
test "zig fmt: switch cases trailing comma" {
try testTransform(
\\test "switch cases trailing comma"{
\\ switch (x) {
\\ 1,2,3 => {},
\\ 4,5, => {},
\\ 6... 8, => {},
\\ else => {},
\\ }
\\}
,
\\test "switch cases trailing comma" {
\\ switch (x) {
\\ 1, 2, 3 => {},
\\ 4,
\\ 5,
\\ => {},
\\ 6...8 => {},
\\ else => {},
\\ }
\\}
\\
);
}
//test "zig fmt: slice align" {
// try testCanonical(
// \\const A = struct {
@@ -1996,16 +1996,16 @@ test "zig fmt: ptr deref operator and unwrap optional operator" {
// \\
// );
//}
//
//test "zig fmt: switch with empty body" {
// try testCanonical(
// \\test "" {
// \\ foo() catch |err| switch (err) {};
// \\}
// \\
// );
//}
//
test "zig fmt: switch with empty body" {
try testCanonical(
\\test "" {
\\ foo() catch |err| switch (err) {};
\\}
\\
);
}
//test "zig fmt: line comments in struct initializer" {
// try testCanonical(
// \\fn foo() void {
@@ -2725,42 +2725,42 @@ test "zig fmt: blocks" {
);
}
//test "zig fmt: switch" {
// try testCanonical(
// \\test "switch" {
// \\ switch (0) {
// \\ 0 => {},
// \\ 1 => unreachable,
// \\ 2, 3 => {},
// \\ 4...7 => {},
// \\ 1 + 4 * 3 + 22 => {},
// \\ else => {
// \\ const a = 1;
// \\ const b = a;
// \\ },
// \\ }
// \\
// \\ const res = switch (0) {
// \\ 0 => 0,
// \\ 1 => 2,
// \\ 1 => a = 4,
// \\ else => 4,
// \\ };
// \\
// \\ const Union = union(enum) {
// \\ Int: i64,
// \\ Float: f64,
// \\ };
// \\
// \\ switch (u) {
// \\ Union.Int => |int| {},
// \\ Union.Float => |*float| unreachable,
// \\ }
// \\}
// \\
// );
//}
//
test "zig fmt: switch" {
try testCanonical(
\\test "switch" {
\\ switch (0) {
\\ 0 => {},
\\ 1 => unreachable,
\\ 2, 3 => {},
\\ 4...7 => {},
\\ 1 + 4 * 3 + 22 => {},
\\ else => {
\\ const a = 1;
\\ const b = a;
\\ },
\\ }
\\
\\ const res = switch (0) {
\\ 0 => 0,
\\ 1 => 2,
\\ 1 => a = 4,
\\ else => 4,
\\ };
\\
\\ const Union = union(enum) {
\\ Int: i64,
\\ Float: f64,
\\ };
\\
\\ switch (u) {
\\ Union.Int => |int| {},
\\ Union.Float => |*float| unreachable,
\\ }
\\}
\\
);
}
//test "zig fmt: while" {
// try testCanonical(
// \\test "while" {

View File

@@ -207,19 +207,18 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
=> {
const statements = [2]ast.Node.Index{ datas[node].lhs, datas[node].rhs };
if (datas[node].lhs == 0) {
return renderBlock(ais, tree, main_tokens[node], statements[0..0], space);
return renderBlock(ais, tree, node, statements[0..0], space);
} else if (datas[node].rhs == 0) {
return renderBlock(ais, tree, main_tokens[node], statements[0..1], space);
return renderBlock(ais, tree, node, statements[0..1], space);
} else {
return renderBlock(ais, tree, main_tokens[node], statements[0..2], space);
return renderBlock(ais, tree, node, statements[0..2], space);
}
},
.Block,
.BlockSemicolon,
=> {
const lbrace = main_tokens[node];
const statements = tree.extra_data[datas[node].lhs..datas[node].rhs];
return renderBlock(ais, tree, main_tokens[node], statements, space);
return renderBlock(ais, tree, node, statements, space);
},
.ErrDefer => {
@@ -615,81 +614,6 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
try renderToken(ais, tree, rbrace, space);
}
},
//.ErrorSetDecl => {
// const err_set_decl = @fieldParentPtr(ast.Node.ErrorSetDecl, "base", base);
// const lbrace = tree.nextToken(err_set_decl.error_token);
// if (err_set_decl.decls_len == 0) {
// try renderToken(ais, tree, err_set_decl.error_token, Space.None);
// try renderToken(ais, tree, lbrace, Space.None);
// return renderToken(ais, tree, err_set_decl.rbrace_token, space);
// }
// if (err_set_decl.decls_len == 1) blk: {
// const node = err_set_decl.decls()[0];
// // if there are any doc comments or same line comments
// // don't try to put it all on one line
// if (node.cast(ast.Node.ErrorTag)) |tag| {
// if (tag.doc_comments != null) break :blk;
// } else {
// break :blk;
// }
// try renderToken(ais, tree, err_set_decl.error_token, Space.None); // error
// try renderToken(ais, tree, lbrace, Space.None); // lbrace
// try renderExpression(ais, tree, node, Space.None);
// return renderToken(ais, tree, err_set_decl.rbrace_token, space); // rbrace
// }
// try renderToken(ais, tree, err_set_decl.error_token, Space.None); // error
// const src_has_trailing_comma = blk: {
// const maybe_comma = tree.prevToken(err_set_decl.rbrace_token);
// break :blk tree.token_tags[maybe_comma] == .Comma;
// };
// if (src_has_trailing_comma) {
// {
// ais.pushIndent();
// defer ais.popIndent();
// try renderToken(ais, tree, lbrace, Space.Newline); // lbrace
// const decls = err_set_decl.decls();
// for (decls) |node, i| {
// if (i + 1 < decls.len) {
// try renderExpression(ais, tree, node, Space.None);
// try renderToken(ais, tree, tree.nextToken(node.lastToken()), Space.Newline); // ,
// try renderExtraNewline(ais, tree, decls[i + 1]);
// } else {
// try renderExpression(ais, tree, node, Space.Comma);
// }
// }
// }
// return renderToken(ais, tree, err_set_decl.rbrace_token, space); // rbrace
// } else {
// try renderToken(ais, tree, lbrace, Space.Space); // lbrace
// const decls = err_set_decl.decls();
// for (decls) |node, i| {
// if (i + 1 < decls.len) {
// try renderExpression(ais, tree, node, Space.None);
// const comma_token = tree.nextToken(node.lastToken());
// assert(tree.token_tags[comma_token] == .Comma);
// try renderToken(ais, tree, comma_token, Space.Space); // ,
// try renderExtraNewline(ais, tree, decls[i + 1]);
// } else {
// try renderExpression(ais, tree, node, Space.Space);
// }
// }
// return renderToken(ais, tree, err_set_decl.rbrace_token, space); // rbrace
// }
//},
.BuiltinCallTwo, .BuiltinCallTwoComma => {
if (datas[node].lhs == 0) {
@@ -732,92 +656,38 @@ fn renderExpression(ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Spac
// }
//},
.Switch => unreachable, // TODO
//.Switch => {
// const switch_node = @fieldParentPtr(ast.Node.Switch, "base", base);
.Switch,
.SwitchComma,
=> {
const switch_token = main_tokens[node];
const condition = datas[node].lhs;
const extra = tree.extraData(datas[node].rhs, ast.Node.SubRange);
const cases = tree.extra_data[extra.start..extra.end];
const rparen = tree.lastToken(condition) + 1;
// try renderToken(ais, tree, switch_node.switch_token, Space.Space); // switch
// try renderToken(ais, tree, tree.nextToken(switch_node.switch_token), Space.None); // (
try renderToken(ais, tree, switch_token, .Space); // switch keyword
try renderToken(ais, tree, switch_token + 1, .None); // lparen
try renderExpression(ais, tree, condition, .None); // condtion expression
try renderToken(ais, tree, rparen, .Space); // rparen
// const rparen = tree.nextToken(switch_node.expr.lastToken());
// const lbrace = tree.nextToken(rparen);
if (cases.len == 0) {
try renderToken(ais, tree, rparen + 1, .None); // lbrace
try renderToken(ais, tree, rparen + 2, space); // rbrace
} else {
try renderToken(ais, tree, rparen + 1, .Newline); // lbrace
ais.pushIndent();
try renderExpression(ais, tree, cases[0], .Comma);
for (cases[1..]) |case| {
try renderExtraNewline(ais, tree, case);
try renderExpression(ais, tree, case, .Comma);
}
ais.popIndent();
try renderToken(ais, tree, tree.lastToken(node), space); // rbrace
}
},
// if (switch_node.cases_len == 0) {
// try renderExpression(ais, tree, switch_node.expr, Space.None);
// try renderToken(ais, tree, rparen, Space.Space); // )
// try renderToken(ais, tree, lbrace, Space.None); // lbrace
// return renderToken(ais, tree, switch_node.rbrace, space); // rbrace
// }
// try renderExpression(ais, tree, switch_node.expr, Space.None);
// try renderToken(ais, tree, rparen, Space.Space); // )
// {
// ais.pushIndentNextLine();
// defer ais.popIndent();
// try renderToken(ais, tree, lbrace, Space.Newline); // lbrace
// const cases = switch_node.cases();
// for (cases) |node, i| {
// try renderExpression(ais, tree, node, Space.Comma);
// if (i + 1 < cases.len) {
// try renderExtraNewline(ais, tree, cases[i + 1]);
// }
// }
// }
// return renderToken(ais, tree, switch_node.rbrace, space); // rbrace
//},
.SwitchCaseOne => unreachable, // TODO
.SwitchCaseMulti => unreachable, // TODO
//.SwitchCase => {
// const switch_case = @fieldParentPtr(ast.Node.SwitchCase, "base", base);
// assert(switch_case.items_len != 0);
// const src_has_trailing_comma = blk: {
// const last_node = switch_case.items()[switch_case.items_len - 1];
// const maybe_comma = tree.nextToken(last_node.lastToken());
// break :blk tree.token_tags[maybe_comma] == .Comma;
// };
// if (switch_case.items_len == 1 or !src_has_trailing_comma) {
// const items = switch_case.items();
// for (items) |node, i| {
// if (i + 1 < items.len) {
// try renderExpression(ais, tree, node, Space.None);
// const comma_token = tree.nextToken(node.lastToken());
// try renderToken(ais, tree, comma_token, Space.Space); // ,
// try renderExtraNewline(ais, tree, items[i + 1]);
// } else {
// try renderExpression(ais, tree, node, Space.Space);
// }
// }
// } else {
// const items = switch_case.items();
// for (items) |node, i| {
// if (i + 1 < items.len) {
// try renderExpression(ais, tree, node, Space.None);
// const comma_token = tree.nextToken(node.lastToken());
// try renderToken(ais, tree, comma_token, Space.Newline); // ,
// try renderExtraNewline(ais, tree, items[i + 1]);
// } else {
// try renderExpression(ais, tree, node, Space.Comma);
// }
// }
// }
// try renderToken(ais, tree, switch_case.arrow_token, Space.Space); // =>
// if (switch_case.payload) |payload| {
// try renderExpression(ais, tree, payload, Space.Space);
// }
// return renderExpression(ais, tree, switch_case.expr, space);
//},
.SwitchCaseOne => try renderSwitchCase(ais, tree, tree.switchCaseOne(node), space),
.SwitchCase => try renderSwitchCase(ais, tree, tree.switchCase(node), space),
.WhileSimple => unreachable, // TODO
.WhileCont => unreachable, // TODO
@@ -1745,16 +1615,64 @@ fn renderFnProto(ais: *Ais, tree: ast.Tree, fn_proto: ast.Full.FnProto, space: S
return renderExpression(ais, tree, fn_proto.ast.return_type, space);
}
fn renderSwitchCase(
ais: *Ais,
tree: ast.Tree,
switch_case: ast.Full.SwitchCase,
space: Space,
) Error!void {
const token_tags = tree.tokens.items(.tag);
const trailing_comma = token_tags[switch_case.ast.arrow_token - 1] == .Comma;
// Render everything before the arrow
if (switch_case.ast.values.len == 0) {
try renderToken(ais, tree, switch_case.ast.arrow_token - 1, .Space); // else keyword
} else if (switch_case.ast.values.len == 1) {
// render on one line and drop the trailing comma if any
try renderExpression(ais, tree, switch_case.ast.values[0], .Space);
} else if (trailing_comma) {
// Render each value on a new line
try renderExpression(ais, tree, switch_case.ast.values[0], .Comma);
for (switch_case.ast.values[1..]) |value_expr| {
try renderExtraNewline(ais, tree, value_expr);
try renderExpression(ais, tree, value_expr, .Comma);
}
} else {
// Render on one line
for (switch_case.ast.values) |value_expr| {
try renderExpression(ais, tree, value_expr, .CommaSpace);
}
}
// Render the arrow and everything after it
try renderToken(ais, tree, switch_case.ast.arrow_token, .Space);
if (switch_case.payload_token) |payload_token| {
try renderToken(ais, tree, payload_token - 1, .None); // pipe
if (token_tags[payload_token] == .Asterisk) {
try renderToken(ais, tree, payload_token, .None); // asterisk
try renderToken(ais, tree, payload_token + 1, .None); // identifier
try renderToken(ais, tree, payload_token + 2, .Space); // pipe
} else {
try renderToken(ais, tree, payload_token, .None); // identifier
try renderToken(ais, tree, payload_token + 1, .Space); // pipe
}
}
try renderExpression(ais, tree, switch_case.ast.target_expr, space);
}
fn renderBlock(
ais: *Ais,
tree: ast.Tree,
lbrace: ast.TokenIndex,
block_node: ast.Node.Index,
statements: []const ast.Node.Index,
space: Space,
) Error!void {
const token_tags = tree.tokens.items(.tag);
const node_tags = tree.nodes.items(.tag);
const nodes_data = tree.nodes.items(.data);
const lbrace = tree.nodes.items(.main_token)[block_node];
if (token_tags[lbrace - 1] == .Colon and
token_tags[lbrace - 2] == .Identifier)
@@ -1783,15 +1701,8 @@ fn renderBlock(
}
}
ais.popIndent();
// The rbrace could be +1 or +2 from the last token of the last
// statement in the block because lastToken() does not count semicolons.
const maybe_rbrace = tree.lastToken(statements[statements.len - 1]) + 1;
if (token_tags[maybe_rbrace] == .RBrace) {
return renderToken(ais, tree, maybe_rbrace, space);
} else {
assert(token_tags[maybe_rbrace + 1] == .RBrace);
return renderToken(ais, tree, maybe_rbrace + 1, space);
}
try renderToken(ais, tree, tree.lastToken(block_node), space); // rbrace
}
// TODO: handle comments between fields