DoxigAlpha

renderExpression

Function parameters

Parameters

#
r:*Render
node:Ast.Node.Index

Type definitions in this namespace

Types

#

Functions in this namespace

Functions

#

Error sets in this namespace

Error Sets

#

Source

Implementation

#
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 + 1) |i| {
                try renderToken(r, @intCast(i), .newline);
            }

            const next_token = last_tok + 1;
            const next_token_tag = tree.tokenTag(next_token);

            // dedent the next thing that comes after a multiline string literal
            if (!ais.indentStackEmpty() and
                next_token_tag != .colon and
                ((next_token_tag != .semicolon and next_token_tag != .comma) or
                    ais.lastSpaceModeIndent() < ais.currentIndent()))
            {
                ais.popIndent();
                try ais.pushIndent(.normal);
            }

            switch (space) {
                .none, .space, .newline, .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),
            }
        },

        .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);
        },

        .@"errdefer" => {
            const defer_token = tree.nodeMainToken(node);
            const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node;

            try renderToken(r, defer_token, .space);
            if (maybe_payload_token.unwrap()) |payload_token| {
                try renderToken(r, payload_token - 1, .none); // |
                try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier
                try renderToken(r, payload_token + 1, .space); // |
            }
            return renderExpression(r, expr, space);
        },

        .@"defer",
        .@"comptime",
        .@"nosuspend",
        .@"suspend",
        => {
            const main_token = tree.nodeMainToken(node);
            const item = tree.nodeData(node).node;
            try renderToken(r, main_token, .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 same_line = tree.tokensOnSameLine(main_token, fallback_first);
            const after_op_space = if (same_line) Space.space else Space.newline;

            try renderExpression(r, lhs, .space); // target

            try ais.pushIndent(.normal);
            if (tree.tokenTag(fallback_first - 1) == .pipe) {
                try renderToken(r, main_token, .space); // catch keyword
                try renderToken(r, main_token + 1, .none); // pipe
                try renderIdentifier(r, main_token + 2, .none, .preserve_when_shadowing); // payload identifier
                try renderToken(r, main_token + 3, after_op_space); // pipe
            } else {
                assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
                try renderToken(r, main_token, after_op_space); // catch keyword
            }
            try renderExpression(r, rhs, space); // fallback
            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);
            try renderExpression(r, lhs, .none);

            // Allow a line break between the lhs and the dot if the lhs and rhs
            // are on different lines.
            const lhs_last_token = tree.lastToken(lhs);
            const same_line = tree.tokensOnSameLine(lhs_last_token, name_token);
            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); // field
            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);
            if (tree.tokensOnSameLine(op_token, op_token + 1)) {
                try renderToken(r, op_token, .space);
            } else {
                try renderToken(r, op_token, .newline);
            }
            try renderExpression(r, rhs, space);
            ais.popIndent();
        },

        .add,
        .add_wrap,
        .add_sat,
        .array_cat,
        .array_mult,
        .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);
            if (tree.tokensOnSameLine(op_token, op_token + 1)) {
                try renderToken(r, op_token, .space);
            } else {
                try renderToken(r, op_token, .newline);
            }
            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, .space);
            }

            for (full.ast.variables, 0..) |variable_node, i| {
                const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_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);
            if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) {
                try renderToken(r, full.ast.equal_token, .space);
            } else {
                try renderToken(r, full.ast.equal_token, .newline);
            }
            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), .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;
            const one_line = tree.tokensOnSameLine(lbracket, rbracket);
            const inner_space = if (one_line) Space.none else Space.newline;
            try renderExpression(r, lhs, .none);
            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;
            if (opt_label_token == .none and opt_target == .none) {
                try renderToken(r, main_token, space); // break/continue
            } else if (opt_label_token == .none and opt_target != .none) {
                const target = opt_target.unwrap().?;
                try renderToken(r, main_token, .space); // break/continue
                try renderExpression(r, target, space);
            } else if (opt_label_token != .none and opt_target == .none) {
                const label_token = opt_label_token.unwrap().?;
                try renderToken(r, main_token, .space); // break/continue
                try renderToken(r, label_token - 1, .none); // :
                try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier
            } else if (opt_label_token != .none and opt_target != .none) {
                const label_token = opt_label_token.unwrap().?;
                const target = opt_target.unwrap().?;
                try renderToken(r, main_token, .space); // break/continue
                try renderToken(r, label_token - 1, .none); // :
                try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier
                try renderExpression(r, target, space);
            } else unreachable;
        },

        .@"return" => {
            if (tree.nodeData(node).opt_node.unwrap()) |expr| {
                try renderToken(r, tree.nodeMainToken(node), .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); // lparen
            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) {
                // There is nothing between the braces so render condensed: `error{}`
                try renderToken(r, lbrace, .none);
                return renderToken(r, rbrace, space);
            } else if (lbrace + 2 == rbrace and tree.tokenTag(lbrace + 1) == .identifier) {
                // There is exactly one member and no trailing comma or
                // comments, so render without surrounding spaces: `error{Foo}`
                try renderToken(r, lbrace, .none);
                try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier
                return renderToken(r, rbrace, space);
            } else if (tree.tokenTag(rbrace - 1) == .comma) {
                // There is a trailing comma so render each member on a new line.
                try ais.pushIndent(.normal);
                try renderToken(r, lbrace, .newline);
                var i = lbrace + 1;
                while (i < rbrace) : (i += 1) {
                    if (i > lbrace + 1) try renderExtraNewlineToken(r, i);
                    switch (tree.tokenTag(i)) {
                        .doc_comment => try renderToken(r, i, .newline),
                        .identifier => {
                            try ais.pushSpace(.comma);
                            try renderIdentifier(r, i, .comma, .eagerly_unquote);
                            ais.popSpace();
                        },
                        .comma => {},
                        else => unreachable,
                    }
                }
                ais.popIndent();
                return renderToken(r, rbrace, space);
            } else {
                // There is no trailing comma so render everything on one line.
                try renderToken(r, lbrace, .space);
                var i = lbrace + 1;
                while (i < rbrace) : (i += 1) {
                    switch (tree.tokenTag(i)) {
                        .doc_comment => unreachable, // TODO
                        .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 {
                    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;
                if (builtin_token >= 2) {
                    var prev_builtin_token = builtin_token - 2;
                    while (tree.tokenTag(prev_builtin_token) == .builtin) : (prev_builtin_token -= 2) {
                        const prev_kind = meta.stringToEnum(CastKind, tree.tokenSlice(prev_builtin_token)[1..]) orelse break;
                        if (cast_map.contains(prev_kind)) break :canonicalize;
                        cast_map.put(prev_kind, prev_builtin_token);
                        casts_before += 1;
                    }
                }

                var next_builtin_token = builtin_token + 2;
                while (tree.tokenTag(next_builtin_token) == .builtin) : (next_builtin_token += 2) {
                    const next_kind = meta.stringToEnum(CastKind, tree.tokenSlice(next_builtin_token)[1..]) orelse break;
                    if (cast_map.contains(next_kind)) break :canonicalize;
                    cast_map.put(next_kind, next_builtin_token);
                }

                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); // anyframe
            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); // label
                try renderToken(r, label_token + 1, .space); // :
            }

            const rparen = tree.lastToken(full.ast.condition) + 1;

            try renderToken(r, full.ast.switch_token, .space); // switch
            try renderToken(r, full.ast.switch_token + 1, .none); // (
            try renderExpression(r, full.ast.condition, .none); // condition expression
            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),

        // To be removed after 0.15.0 is tagged
        .asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),

        .enum_literal => {
            try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
            return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
        },

        .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,
    }
}