feature. See also
. The project being documented here (as the example) is the Zig library itself.
Render.renderExpression
fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void
File
Code
fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
const tree = r.tree;
const ais = r.ais;
if (r.fixups.replace_nodes_with_string.get(node)) |replacement| {
try ais.writeAll(replacement);
try renderOnlySpace(r, space);
return;
} else if (r.fixups.replace_nodes_with_node.get(node)) |replacement| {
return renderExpression(r, replacement, space);
}
switch (tree.nodeTag(node)) {
.identifier => {
const token_index = tree.nodeMainToken(node);
return renderIdentifier(r, token_index, space, .preserve_when_shadowing);
},
.number_literal,
.char_literal,
.unreachable_literal,
.anyframe_literal,
.string_literal,
=> return renderToken(r, tree.nodeMainToken(node), space),
.multiline_string_literal => {
try ais.maybeInsertNewline();
const first_tok, const last_tok = tree.nodeData(node).token_and_token;
for (first_tok..last_tok) |i| {
try renderToken(r, @intCast(i), .newline);
}
if (space != .skip) {
try renderToken(r, last_tok, .newline);
} else {
try renderToken(r, last_tok, .skip);
try ais.insertNewline();
// rendered here.
}
const next_token = last_tok + 1;
const next_token_tag = tree.tokenTag(next_token);
if (next_token_tag != .colon and
!ais.indentStackEmpty() and
ais.lastSpaceModeIndent() < ais.currentIndent())
{
const indent_top = &ais.indent_stack.items[ais.indent_stack.items.len - 1];
if (indent_top.realized) {
indent_top.realized = false;
ais.indent_count -= 1;
}
}
switch (space) {
.none, .space, .newline, .maybe_space, .skip => {},
.semicolon => if (next_token_tag == .semicolon)
try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon),
.comma => if (next_token_tag == .comma)
try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma),
.comma_space => if (next_token_tag == .comma)
try renderToken(r, next_token, .space),
.comma_maybe_space => if (next_token_tag == .comma)
try renderToken(r, next_token, .maybe_space),
}
},
.error_value => {
const main_token = tree.nodeMainToken(node);
try renderToken(r, main_token, .none);
try renderToken(r, main_token + 1, .none);
return renderIdentifier(r, main_token + 2, space, .eagerly_unquote);
},
.block_two,
.block_two_semicolon,
.block,
.block_semicolon,
=> {
var buf: [2]Ast.Node.Index = undefined;
const statements = tree.blockStatements(&buf, node).?;
return renderBlock(r, node, statements, space);
},
.@"defer",
.@"errdefer",
.@"comptime",
.@"nosuspend",
.@"suspend",
=> {
const main_token = tree.nodeMainToken(node);
const item = tree.nodeData(node).node;
try renderToken(r, main_token, .maybe_space);
return renderExpression(r, item, space);
},
.@"catch" => {
const main_token = tree.nodeMainToken(node);
const lhs, const rhs = tree.nodeData(node).node_and_node;
const fallback_first = tree.firstToken(rhs);
const seperate_line = !tree.tokensOnSameLine(main_token, fallback_first) or
tree.tokenTag(fallback_first) == .multiline_string_literal_line;
const after_op_space: Space = if (seperate_line) .newline else .space;
try renderExpression(r, lhs, .space);
try ais.pushIndent(.normal);
if (tree.tokenTag(fallback_first - 1) == .pipe) {
try renderToken(r, main_token, .space);
try renderToken(r, main_token + 1, .none);
try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing);
try renderToken(r, main_token + 3, after_op_space);
} else {
assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
try renderToken(r, main_token, after_op_space);
}
try renderExpression(r, rhs, space);
ais.popIndent();
},
.field_access => {
const lhs, const name_token = tree.nodeData(node).node_and_token;
const dot_token = name_token - 1;
try ais.pushIndent(.field_access);
const lhs_last_token = tree.lastToken(lhs);
const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
// (e.g. "0xF .A").
const number_space: Space = if (tree.tokenTag(lhs_last_token) == .number_literal and same_line) .space else .none;
try renderExpression(r, lhs, number_space);
// are on different lines.
if (!same_line and !hasComment(tree, lhs_last_token, dot_token))
try ais.insertNewline();
try renderToken(r, dot_token, .none);
try renderIdentifier(r, name_token, space, .eagerly_unquote);
ais.popIndent();
},
.error_union,
.switch_range,
=> {
const lhs, const rhs = tree.nodeData(node).node_and_node;
try renderExpression(r, lhs, .none);
try renderToken(r, tree.nodeMainToken(node), .none);
return renderExpression(r, rhs, space);
},
.for_range => {
const start, const opt_end = tree.nodeData(node).node_and_opt_node;
try renderExpression(r, start, .none);
if (opt_end.unwrap()) |end| {
try renderToken(r, tree.nodeMainToken(node), .none);
return renderExpression(r, end, space);
} else {
return renderToken(r, tree.nodeMainToken(node), space);
}
},
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_shl,
.assign_shl_sat,
.assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
.assign_sub_sat,
.assign_mod,
.assign_add,
.assign_add_wrap,
.assign_add_sat,
.assign_mul,
.assign_mul_wrap,
.assign_mul_sat,
=> {
const lhs, const rhs = tree.nodeData(node).node_and_node;
try renderExpression(r, lhs, .space);
const op_token = tree.nodeMainToken(node);
try ais.pushIndent(.after_equals);
const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or
tree.tokenTag(op_token + 1) == .multiline_string_literal_line;
try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space);
try renderExpression(r, rhs, space);
ais.popIndent();
},
.add,
.add_wrap,
.add_sat,
.array_cat,
.bang_equal,
.bit_and,
.bit_or,
.shl,
.shl_sat,
.shr,
.bit_xor,
.bool_and,
.bool_or,
.div,
.equal_equal,
.greater_or_equal,
.greater_than,
.less_or_equal,
.less_than,
.merge_error_sets,
.mod,
.mul,
.mul_wrap,
.mul_sat,
.sub,
.sub_wrap,
.sub_sat,
.@"orelse",
=> {
const lhs, const rhs = tree.nodeData(node).node_and_node;
try renderExpression(r, lhs, .space);
const op_token = tree.nodeMainToken(node);
try ais.pushIndent(.binop);
const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or
tree.tokenTag(op_token + 1) == .multiline_string_literal_line;
try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space);
try renderExpression(r, rhs, space);
ais.popIndent();
},
.assign_destructure => {
const full = tree.assignDestructure(node);
if (full.comptime_token) |comptime_token| {
try renderToken(r, comptime_token, .maybe_space);
}
for (full.ast.variables, 0..) |variable_node, i| {
const variable_space: Space = if (i == full.ast.variables.len - 1) .maybe_space else .comma_maybe_space;
switch (tree.nodeTag(variable_node)) {
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> {
try renderVarDecl(r, tree.fullVarDecl(variable_node).?, true, variable_space);
},
else => try renderExpression(r, variable_node, variable_space),
}
}
try ais.pushIndent(.after_equals);
const expr_seperate_line =
!tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1) or
tree.tokenTag(full.ast.equal_token + 1) == .multiline_string_literal_line;
try renderToken(r, full.ast.equal_token, if (expr_seperate_line) .newline else .space);
try renderExpression(r, full.ast.value_expr, space);
ais.popIndent();
},
.bit_not,
.bool_not,
.negation,
.negation_wrap,
.optional_type,
.address_of,
=> {
try renderToken(r, tree.nodeMainToken(node), .none);
return renderExpression(r, tree.nodeData(node).node, space);
},
.@"try",
.@"resume",
=> {
try renderToken(r, tree.nodeMainToken(node), .maybe_space);
return renderExpression(r, tree.nodeData(node).node, space);
},
.array_type,
.array_type_sentinel,
=> return renderArrayType(r, tree.fullArrayType(node).?, space),
.ptr_type_aligned,
.ptr_type_sentinel,
.ptr_type,
.ptr_type_bit_range,
=> return renderPtrType(r, tree.fullPtrType(node).?, space),
.array_init_one,
.array_init_one_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init,
.array_init_comma,
=> {
var elements: [2]Ast.Node.Index = undefined;
return renderArrayInit(r, tree.fullArrayInit(&elements, node).?, space);
},
.struct_init_one,
.struct_init_one_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init,
.struct_init_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
return renderStructInit(r, node, tree.fullStructInit(&buf, node).?, space);
},
.call_one,
.call_one_comma,
.call,
.call_comma,
=> {
var buf: [1]Ast.Node.Index = undefined;
return renderCall(r, tree.fullCall(&buf, node).?, space);
},
.array_access => {
const lhs, const rhs = tree.nodeData(node).node_and_node;
const lbracket = tree.firstToken(rhs) - 1;
const rbracket = tree.lastToken(rhs) + 1;
try renderExpression(r, lhs, .none);
// isLineOverIndented
const one_line = tree.tokensOnSameLine(lbracket, rbracket) and
!try rendersMultiline(r, rhs);
const inner_space = if (one_line) Space.none else Space.newline;
try ais.pushIndent(.normal);
try renderToken(r, lbracket, inner_space);
try renderExpression(r, rhs, inner_space);
ais.popIndent();
return renderToken(r, rbracket, space);
},
.slice_open,
.slice,
.slice_sentinel,
=> return renderSlice(r, node, tree.fullSlice(node).?, space),
.deref => {
try renderExpression(r, tree.nodeData(node).node, .none);
return renderToken(r, tree.nodeMainToken(node), space);
},
.unwrap_optional => {
const lhs, const question_mark = tree.nodeData(node).node_and_token;
const dot_token = question_mark - 1;
try renderExpression(r, lhs, .none);
try renderToken(r, dot_token, .none);
return renderToken(r, question_mark, space);
},
.@"break", .@"continue" => {
const main_token = tree.nodeMainToken(node);
const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node;
const before_target_space: Space = if (opt_target != .none) .maybe_space else space;
const before_label_space: Space = if (opt_label_token != .none) .space else before_target_space;
try renderToken(r, main_token, before_label_space);
if (opt_label_token.unwrap()) |label_token| {
try renderToken(r, label_token - 1, .none);
try renderIdentifier(r, label_token, before_target_space, .eagerly_unquote);
}
if (opt_target.unwrap()) |target| {
try renderExpression(r, target, space);
}
},
.@"return" => {
if (tree.nodeData(node).opt_node.unwrap()) |expr| {
try renderToken(r, tree.nodeMainToken(node), .maybe_space);
try renderExpression(r, expr, space);
} else {
try renderToken(r, tree.nodeMainToken(node), space);
}
},
.grouped_expression => {
const expr, const rparen = tree.nodeData(node).node_and_token;
try ais.pushIndent(.normal);
try renderToken(r, tree.nodeMainToken(node), .none);
try renderExpression(r, expr, .none);
ais.popIndent();
return renderToken(r, rparen, space);
},
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
=> {
var buf: [2]Ast.Node.Index = undefined;
return renderContainerDecl(r, node, tree.fullContainerDecl(&buf, node).?, space);
},
.error_set_decl => {
const error_token = tree.nodeMainToken(node);
const lbrace, const rbrace = tree.nodeData(node).token_and_token;
try renderToken(r, error_token, .none);
if (lbrace + 1 == rbrace) {
try renderToken(r, lbrace, .none);
return renderToken(r, rbrace, space);
} else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
// comments, so render without surrounding spaces: `error{Foo}`
try renderToken(r, lbrace, .none);
try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote);
return renderToken(r, rbrace, space);
} else if (!isOneLineErrorSetDecl(tree, lbrace, rbrace)) {
try ais.pushIndent(.normal);
try renderToken(r, lbrace, .newline);
var i = lbrace + 1;
while (i < rbrace) : (i += 1) {
const tag = tree.tokenTag(i);
if (tag == .comma) {
assert(tree.tokenTag(i - 1) == .identifier);
continue;
}
if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
switch (tag) {
.doc_comment => try renderToken(r, i, .newline),
.identifier => {
try ais.pushSpace(.comma);
try renderIdentifier(r, i, .comma, .eagerly_unquote);
ais.popSpace();
},
else => unreachable,
}
}
ais.popIndent();
return renderToken(r, rbrace, space);
} else {
try renderToken(r, lbrace, .space);
var i = lbrace + 1;
while (i < rbrace) : (i += 1) {
switch (tree.tokenTag(i)) {
.identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote),
.comma => {},
else => unreachable,
}
}
return renderToken(r, rbrace, space);
}
},
.builtin_call_two,
.builtin_call_two_comma,
.builtin_call,
.builtin_call_comma,
=> {
var buf: [2]Ast.Node.Index = undefined;
const params = tree.builtinCallParams(&buf, node).?;
var builtin_token = tree.nodeMainToken(node);
canonicalize: {
if (params.len != 1) break :canonicalize;
const CastKind = enum(u8) {
ptrCast,
alignCast,
addrSpaceCast,
constCast,
volatileCast,
};
const kind = meta.stringToEnum(
CastKind,
tree.tokenSlice(builtin_token)[1..],
) orelse break :canonicalize;
var cast_map = std.EnumMap(CastKind, Ast.TokenIndex).init(.{});
cast_map.put(kind, builtin_token);
var casts_before: usize = 0;
var prev_builtin_token = builtin_token;
while (prev_builtin_token >= 2) {
prev_builtin_token -= 2;
if (tree.tokenTag(prev_builtin_token) != .builtin) break;
const builtin_name = tree.tokenSlice(prev_builtin_token)[1..];
const prev_kind = meta.stringToEnum(CastKind, builtin_name) orelse break;
if (cast_map.contains(prev_kind)) break :canonicalize;
// builtins containing comments are reordered.
if (hasComment(tree, prev_builtin_token, prev_builtin_token + 2))
break :canonicalize;
cast_map.put(prev_kind, prev_builtin_token);
casts_before += 1;
}
var next_builtin_token = builtin_token + 2;
while (true) {
if (hasComment(tree, next_builtin_token - 2, next_builtin_token))
break :canonicalize;
if (tree.tokenTag(next_builtin_token) != .builtin) break;
const builtin_name = tree.tokenSlice(next_builtin_token)[1..];
const next_kind = meta.stringToEnum(CastKind, builtin_name) orelse break;
if (cast_map.contains(next_kind)) break :canonicalize;
cast_map.put(next_kind, next_builtin_token);
next_builtin_token += 2;
}
var it = cast_map.iterator();
builtin_token = it.next().?.value.*;
while (casts_before > 0) : (casts_before -= 1) {
builtin_token = it.next().?.value.*;
}
}
return renderBuiltinCall(r, builtin_token, params, space);
},
.fn_proto_simple,
.fn_proto_multi,
.fn_proto_one,
.fn_proto,
=> {
var buf: [1]Ast.Node.Index = undefined;
return renderFnProto(r, tree.fullFnProto(&buf, node).?, space);
},
.anyframe_type => {
const main_token = tree.nodeMainToken(node);
try renderToken(r, main_token, .none);
try renderToken(r, main_token + 1, .none);
return renderExpression(r, tree.nodeData(node).token_and_node[1], space);
},
.@"switch",
.switch_comma,
=> {
const full = tree.switchFull(node);
if (full.label_token) |label_token| {
try renderIdentifier(r, label_token, .none, .eagerly_unquote);
try renderToken(r, label_token + 1, .space);
}
const rparen = tree.lastToken(full.ast.condition) + 1;
try renderToken(r, full.ast.switch_token, .space);
try renderToken(r, full.ast.switch_token + 1, .none);
try renderExpression(r, full.ast.condition, .none);
try renderToken(r, rparen, .space);
try ais.pushIndent(.normal);
if (full.ast.cases.len == 0) {
try renderToken(r, rparen + 1, .none);
} else {
try renderToken(r, rparen + 1, .newline);
try ais.pushSpace(.comma);
try renderExpressions(r, full.ast.cases, .comma);
ais.popSpace();
}
ais.popIndent();
return renderToken(r, tree.lastToken(node), space);
},
.switch_case_one,
.switch_case_inline_one,
.switch_case,
.switch_case_inline,
=> return renderSwitchCase(r, tree.fullSwitchCase(node).?, space),
.while_simple,
.while_cont,
.@"while",
=> return renderWhile(r, tree.fullWhile(node).?, space),
.for_simple,
.@"for",
=> return renderFor(r, tree.fullFor(node).?, space),
.if_simple,
.@"if",
=> return renderIf(r, tree.fullIf(node).?, space),
.asm_simple,
.@"asm",
=> return renderAsm(r, tree.fullAsm(node).?, space),
.enum_literal => {
try renderToken(r, tree.nodeMainToken(node) - 1, .none);
return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote);
},
.fn_decl => unreachable,
.container_field => unreachable,
.container_field_init => unreachable,
.container_field_align => unreachable,
.root => unreachable,
.global_var_decl => unreachable,
.local_var_decl => unreachable,
.simple_var_decl => unreachable,
.aligned_var_decl => unreachable,
.test_decl => unreachable,
.asm_output => unreachable,
.asm_input => unreachable,
}
}