
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Dmitry Volyntsev
 * Copyright (C) Alexander Borisov
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


#define NJS_FUNCTION_MAX_DEPTH  128


typedef struct njs_generator_patch_s   njs_generator_patch_t;

typedef enum {
    NJS_GENERATOR_LOOP = 1,
    NJS_GENERATOR_SWITCH = 2,
    NJS_GENERATOR_BLOCK = 4,
    NJS_GENERATOR_TRY = 8,
#define NJS_GENERATOR_ALL          (NJS_GENERATOR_LOOP | NJS_GENERATOR_SWITCH)
} njs_generator_block_type_t;


typedef enum {
    NJS_GENERATOR_CONTINUATION = 1,
    NJS_GENERATOR_EXIT = 2,
} njs_generator_patch_type_t;


struct njs_generator_patch_s {
    /*
     * The jump_offset field points to jump offset field which contains a small
     * adjustment and the adjustment should be added as (njs_jump_off_t *)
     * because pointer to u_char accesses only one byte so this does not
     * work on big endian platforms.
     */
    njs_jump_off_t                  jump_offset;
    njs_generator_patch_t           *next;

    njs_str_t                       label;
};


struct njs_generator_block_s {
    njs_generator_block_type_t      type;    /* 4 bits */
    njs_str_t                       label;

    /* list of "continue" instruction offsets to be patched. */
    njs_generator_patch_t           *continuation;
    /*
     * list of "return" from try-catch block and "break"
     * instruction offsets to be patched.
     */
    njs_generator_patch_t           *exit;

    njs_generator_block_t           *next;

    /* exit value index, used only for NJS_GENERATOR_TRY blocks. */
    njs_index_t                     index;
};


typedef struct {
    njs_generator_state_func_t  state;
    njs_queue_link_t            link;
    njs_parser_node_t           *node;
    void                        *context;
} njs_generator_stack_entry_t;


typedef struct {
    njs_generator_patch_t       *patch;
    njs_generator_patch_t       **last;
    njs_vmcode_jump_t           *jump;
    njs_jump_off_t              jump_offset;
    njs_index_t                 index;
} njs_generator_switch_ctx_t;


typedef struct {
    njs_jump_off_t              jump_offset;
    njs_jump_off_t              loop_offset;
    njs_vmcode_jump_t           *jump;
    njs_variable_t              *var;
    njs_index_t                 index;
    njs_index_t                 index_next_value;
} njs_generator_loop_ctx_t;


typedef struct {
    njs_index_t                 exception_index;
    njs_jump_off_t              try_offset;
    njs_jump_off_t              catch_offset;
    njs_generator_block_t       *try_block;
    njs_generator_block_t       *catch_block;
    njs_str_t                   try_cont_label;
    njs_str_t                   try_exit_label;
    njs_str_t                   catch_cont_label;
    njs_str_t                   catch_exit_label;
} njs_generator_try_ctx_t;


typedef struct {
    njs_jump_off_t  jump_offset;
    njs_index_t     prop_index;
} njs_generator_log_assign_ctx_t;


njs_inline njs_bool_t
njs_generate_is_property_lvalue(njs_parser_node_t *node)
{
    return node->token_type == NJS_TOKEN_PROPERTY
           || node->token_type == NJS_TOKEN_PROPERTY_REF;
}


njs_inline njs_bool_t
njs_generate_is_property_call_source(njs_parser_node_t *node)
{
    return node != NULL && node->token_type == NJS_TOKEN_PROPERTY_REF;
}


njs_inline njs_parser_node_t *
njs_generate_wrapped_node(njs_parser_node_t *node)
{
    njs_assert(node->token_type == NJS_TOKEN_OBJECT_VALUE
               || node->token_type == NJS_TOKEN_OPTIONAL_PRESERVE);

    return node->u.object;
}


njs_inline njs_parser_node_t *
njs_generate_object_value_parent(njs_parser_node_t *node)
{
    njs_assert(node->token_type == NJS_TOKEN_OBJECT_VALUE);

    return njs_generate_wrapped_node(node);
}


njs_inline njs_bool_t
njs_generate_is_object_literal_value(njs_parser_node_t *node)
{
    return node != NULL
           && node->token_type == NJS_TOKEN_OBJECT_VALUE
           && njs_generate_object_value_parent(node)->token_type
              == NJS_TOKEN_OBJECT;
}


njs_inline njs_parser_node_t *
njs_generate_optional_method_call_preserve(njs_parser_node_t *node)
{
    njs_assert(node->token_type == NJS_TOKEN_METHOD_CALL);

    return node->u.object;
}


njs_inline njs_parser_node_t *
njs_generate_optional_method_call_property(njs_parser_node_t *node)
{
    return node->left;
}


njs_inline njs_parser_node_t *
njs_generate_optional_method_call_base(njs_parser_node_t *node)
{
    njs_parser_node_t  *preserve;

    preserve = njs_generate_optional_method_call_preserve(node);

    return (preserve != NULL) ? preserve->left : NULL;
}


njs_inline njs_parser_node_t *
njs_generate_optional_method_call_key(njs_parser_node_t *node)
{
    njs_parser_node_t  *preserve;

    preserve = njs_generate_optional_method_call_preserve(node);

    return (preserve != NULL) ? preserve->right : NULL;
}


njs_inline njs_parser_node_t *
njs_generate_optional_chain_preserve(njs_parser_node_t *node)
{
    njs_assert(node->token_type == NJS_TOKEN_OPTIONAL_CHAIN);

    return node->u.object;
}


njs_inline njs_parser_node_t *
njs_generate_function_call_this(njs_parser_node_t *node)
{
    njs_assert(node->token_type == NJS_TOKEN_FUNCTION_CALL);

    return node->u.object;
}


static u_char *njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator,
    size_t size);
static njs_int_t njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, u_char *code);

static njs_int_t njs_generate_name(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_reference_type_t type,
    njs_variable_t **retvar);
static njs_int_t njs_generate_var_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_var_statement_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_let(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_variable_t *var);
static njs_int_t njs_generate_if_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_if_statement_cond(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_if_statement_then(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_if_statement_else(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_cond_expression(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_cond_expression_handler(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_cond_expression_true(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_cond_expression_false(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_switch_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_switch_expression(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *swtch);
static njs_int_t njs_generate_switch_case(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *swtch);
static njs_int_t njs_generate_switch_case_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *branch);
static njs_int_t njs_generate_switch_case_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *branch);
static njs_int_t njs_generate_switch_default(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *branch);
static njs_int_t njs_generate_switch_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *swtch);
static njs_int_t njs_generate_while_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_while_condition(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_while_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_do_while_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_do_while_condition(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_do_while_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_init(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_for_body(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_for_update(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_for_let_update(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_resolve_closure(njs_vm_t *vm,
    njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_set_prop_block(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_name_assign(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_object(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_object_wo_decl(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_object_left_hand_expr(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_body(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_body_wo_decl(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_for_in_body_left_hand_expr(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_start_block(njs_vm_t *vm,
    njs_generator_t *generator, njs_generator_block_type_t type,
    const njs_str_t *label);
static njs_generator_block_t *njs_generate_lookup_block(
    njs_generator_block_t *block, uint32_t mask, const njs_str_t *label);
static njs_generator_block_t *njs_generate_find_block(njs_vm_t *vm,
    njs_generator_block_t *block, uint32_t mask, const njs_str_t *label);
static void njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator,
    njs_generator_block_t *block, unsigned type);
static njs_generator_patch_t *njs_generate_make_continuation_patch(njs_vm_t *vm,
    njs_generator_block_t *block, const njs_str_t *label,
    njs_jump_off_t offset);
static njs_generator_patch_t *njs_generate_make_exit_patch(njs_vm_t *vm,
    njs_generator_block_t *block, const njs_str_t *label,
    njs_jump_off_t offset);
static void njs_generate_patch_block_exit(njs_vm_t *vm,
    njs_generator_t *generator);
static const njs_str_t *njs_generate_jump_destination(njs_vm_t *vm,
    njs_generator_block_t *block, const char *inst_type, uint32_t mask,
    const njs_str_t *label1, const njs_str_t *label2);
static njs_int_t njs_generate_continue_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_break_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_debugger_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_block_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_block_statement_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_children(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_stop_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_stop_statement_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_comma_expression(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_comma_expression_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_global_property_set(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node_dst,
    njs_parser_node_t *node_src);
static njs_int_t njs_generate_property_lvalue(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node,
    njs_generator_state_func_t after, void *ctx, size_t ctx_size);
static njs_int_t njs_generate_preserve_property_lvalue(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *lvalue,
    njs_parser_node_t *expr);
static njs_int_t njs_generate_read_property_assignment(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node,
    njs_parser_node_t *expr, njs_index_t *prop_index);
static njs_index_t njs_generate_property_index(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node,
    njs_parser_node_t *object, njs_parser_node_t *property);
static njs_vmcode_t njs_generate_property_get_opcode(njs_parser_node_t *node);
static njs_vmcode_t njs_generate_property_set_opcode(njs_parser_node_t *node);
static njs_int_t njs_generate_property_get(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node, njs_index_t value,
    njs_index_t object, njs_index_t property);
static njs_int_t njs_generate_property_set(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node, njs_index_t value,
    njs_index_t object, njs_index_t property);
static njs_int_t njs_generate_assignment(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_assignment_name(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_assignment_prop(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_assignment_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_operation_assignment(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_operation_assignment_name(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_operation_assignment_prop(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_operation_assignment_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_logical_assignment(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_logical_assignment_prop(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_logical_assignment_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_object(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_property_accessor(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_property_accessor_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_array(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_function_expression(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_regexp(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_template_literal(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_template_literal_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_test_jump(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node, njs_vmcode_t opcode,
    njs_index_t value, njs_index_t retval, njs_jump_off_t *jump_offset);
static njs_int_t njs_generate_test_jump_expression(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_test_jump_expression_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_test_jump_expression_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_optional_chain(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_optional_chain_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_optional_chain_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_3addr_operation(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_3addr_operation_name(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_3addr_operation_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_2addr_operation(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_2addr_operation_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_typeof_operation(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_typeof_operation_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_inc_dec_operation(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node, njs_bool_t post);
static njs_int_t njs_generate_inc_dec_operation_prop(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function_declaration(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function_scope(njs_vm_t *vm,
    njs_generator_t *generator, njs_function_lambda_t *lambda,
    njs_parser_node_t *node, const njs_str_t *name);
static njs_int_t njs_generate_scope_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static int64_t njs_generate_lambda_variables(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_return_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_return_statement_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function_call(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function_call_arguments(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function_call_frame(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_function_call_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_method_call(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_method_call_arguments(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_method_call_prepare(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_method_call_frame(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_call_argument_expressions(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_call_argument_expressions_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_uint_t njs_generate_call_nargs(njs_parser_node_t *node);
static njs_int_t njs_generate_capture_stable_value(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_method_call_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_call(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_try_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_try_left(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_try_catch(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_try_finally(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_try_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_throw_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_throw_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_import_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_export_statement(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_export_statement_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_await(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node);
static njs_int_t njs_generate_await_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_wo_dest(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_wo_dest_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_global_reference(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node, njs_bool_t exception);
static njs_int_t njs_generate_reference_error(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);

static njs_index_t njs_generate_dest_index(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_index_t njs_generate_object_dest_index(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_index_t njs_generate_node_temp_index_get(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_index_t njs_generate_temp_index_get(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_children_indexes_release(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_node_index_release(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_node_index_release_pop(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node);
static njs_int_t njs_generate_index_release(njs_vm_t *vm,
    njs_generator_t *generator, njs_index_t index);


#define njs_generate_code(generator, type, _code, _op, nd)                    \
    do {                                                                      \
        _code = (type *) njs_generate_reserve(vm, generator, sizeof(type));   \
        if (njs_slow_path(_code == NULL)) {                                   \
            return NJS_ERROR;                                                 \
        }                                                                     \
                                                                              \
        if (njs_generate_code_map(vm, generator, nd, (u_char *) _code)        \
            != NJS_OK)                                                        \
        {                                                                     \
            return NJS_ERROR;                                                 \
        }                                                                     \
                                                                              \
        generator->code_end += sizeof(type);                                  \
                                                                              \
        _code->code = _op;                                                    \
    } while (0)


#define njs_generate_code_jump(generator, _code, _offset)                     \
    do {                                                                      \
        njs_generate_code(generator, njs_vmcode_jump_t, _code,                \
                          NJS_VMCODE_JUMP, NULL);                             \
        _code->offset = _offset;                                              \
    } while (0)


#define njs_generate_code_move(generator, _code, _dst, _src, node)            \
    do {                                                                      \
        njs_generate_code(generator, njs_vmcode_move_t, _code,                \
                          NJS_VMCODE_MOVE, node);                             \
        _code->dst = _dst;                                                    \
        _code->src = _src;                                                    \
    } while (0)


#define njs_code_offset(generator, code)                                      \
    ((u_char *) code - generator->code_start)


#define njs_code_ptr(generator, type, offset)                                 \
    (type *) (generator->code_start + offset)


#define njs_code_jump_ptr(generator, offset)                                  \
    (njs_jump_off_t *) (generator->code_start + offset)


#define njs_code_offset_diff(generator, offset)                               \
    ((generator->code_end - generator->code_start) - offset)


#define njs_code_set_offset(generator, offset, target)                        \
    *(njs_code_jump_ptr(generator, offset))                                   \
        = njs_code_offset_diff(generator, target)


#define njs_code_set_jump_offset(generator, type, code_offset)                \
    *(njs_code_jump_ptr(generator, code_offset + offsetof(type, offset)))     \
        = njs_code_offset_diff(generator, code_offset)


#define njs_code_update_offset(generator, patch)                              \
    *(njs_code_jump_ptr(generator, patch->jump_offset)) +=                    \
        njs_code_offset_diff(generator, patch->jump_offset)


#define njs_generate_syntax_error(vm, node, file, fmt, ...)                   \
    njs_parser_node_error(vm, NJS_OBJ_TYPE_SYNTAX_ERROR, node, file, fmt,     \
                          ##__VA_ARGS__)


#ifdef NJS_DEBUG_GENERATOR
#define njs_debug_generator(vm, msg, ...)                                     \
        if (vm->options.generator_debug)                                      \
            njs_printf("GENERATOR " msg "\n", ##__VA_ARGS__)
#define njs_debug_generator_code(code)                                        \
        if (vm->options.generator_debug)                                      \
            njs_disassemble((u_char *) code, NULL, 1, NULL)
#else
#define njs_debug_generator(vm, msg, ...)
#define njs_debug_generator_code(code)
#endif


static const njs_str_t  no_label     = njs_str("");
static const njs_str_t  return_label = njs_str("@return");
/* GCC and Clang complain about NULL argument passed to memcmp(). */
static const njs_str_t  undef_label  = { 0xffffffff, (u_char *) "" };


njs_int_t
njs_generator_init(njs_generator_t *generator, njs_str_t *file,
    njs_int_t depth, njs_bool_t runtime)
{
    njs_memzero(generator, sizeof(njs_generator_t));

    njs_queue_init(&generator->stack);

    generator->file = *file;
    generator->depth = depth;
    generator->runtime = runtime;

    return NJS_OK;
}


njs_inline void
njs_generator_next(njs_generator_t *generator, njs_generator_state_func_t state,
    njs_parser_node_t *node)
{
    generator->state = state;
    generator->node = node;
}


njs_inline njs_int_t
njs_generator_after(njs_vm_t *vm, njs_generator_t *generator,
    njs_queue_link_t *link, njs_parser_node_t *node,
    njs_generator_state_func_t state, void *ctx, size_t size)
{
    njs_generator_stack_entry_t  *entry;

    entry = njs_mp_alloc(vm->mem_pool, sizeof(njs_generator_stack_entry_t));
    if (njs_slow_path(entry == NULL)) {
        return NJS_ERROR;
    }

    entry->state = state;
    entry->node = node;
    entry->context = ctx;

    njs_queue_insert_before(link, &entry->link);

    if (size > 0) {
        entry->context = njs_mp_alloc(vm->mem_pool, size);
        if (njs_slow_path(entry->context == NULL)) {
            return NJS_ERROR;
        }

        memcpy(entry->context, ctx, size);
    }

    return NJS_OK;
}


njs_inline njs_int_t
njs_generator_stack_pop(njs_vm_t *vm, njs_generator_t *generator, void *ctx)
{
    njs_queue_link_t             *link;
    njs_generator_stack_entry_t  *entry;

    entry = njs_queue_link_data(njs_queue_first(&generator->stack),
                                njs_generator_stack_entry_t, link);

    link = njs_queue_first(&generator->stack);
    njs_queue_remove(link);

    if (ctx != NULL) {
        njs_mp_free(vm->mem_pool, ctx);
    }

    generator->context = entry->context;

    njs_generator_next(generator, entry->state, entry->node);

    njs_mp_free(vm->mem_pool, entry);

    return NJS_OK;
}


static njs_int_t
njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node)
{
    if (node == NULL) {
        return njs_generator_stack_pop(vm, generator, NULL);
    }

    switch (node->token_type) {

    case NJS_TOKEN_VAR:
    case NJS_TOKEN_LET:
    case NJS_TOKEN_CONST:
        return njs_generate_var_statement(vm, generator, node);

    case NJS_TOKEN_IF:
        return njs_generate_if_statement(vm, generator, node);

    case NJS_TOKEN_CONDITIONAL:
        return njs_generate_cond_expression(vm, generator, node);

    case NJS_TOKEN_SWITCH:
        return njs_generate_switch_statement(vm, generator, node);

    case NJS_TOKEN_WHILE:
        return njs_generate_while_statement(vm, generator, node);

    case NJS_TOKEN_DO:
        return njs_generate_do_while_statement(vm, generator, node);

    case NJS_TOKEN_FOR:
        return njs_generate_for_statement(vm, generator, node);

    case NJS_TOKEN_FOR_IN:
        return njs_generate_for_in_statement(vm, generator, node);

    case NJS_TOKEN_CONTINUE:
        return njs_generate_continue_statement(vm, generator, node);

    case NJS_TOKEN_BREAK:
        return njs_generate_break_statement(vm, generator, node);

    case NJS_TOKEN_DEBUGGER:
        return njs_generate_debugger_statement(vm, generator, node);

    case NJS_TOKEN_STATEMENT:
        return njs_generate_statement(vm, generator, node);

    case NJS_TOKEN_BLOCK:
        return njs_generate_block_statement(vm, generator, node);

    case NJS_TOKEN_END:
        return njs_generate_stop_statement(vm, generator, node);

    case NJS_TOKEN_COMMA:
        return njs_generate_comma_expression(vm, generator, node);

    case NJS_TOKEN_ASSIGNMENT:
        return njs_generate_assignment(vm, generator, node);

    case NJS_TOKEN_BITWISE_OR_ASSIGNMENT:
    case NJS_TOKEN_BITWISE_XOR_ASSIGNMENT:
    case NJS_TOKEN_BITWISE_AND_ASSIGNMENT:
    case NJS_TOKEN_LEFT_SHIFT_ASSIGNMENT:
    case NJS_TOKEN_RIGHT_SHIFT_ASSIGNMENT:
    case NJS_TOKEN_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
    case NJS_TOKEN_ADDITION_ASSIGNMENT:
    case NJS_TOKEN_SUBTRACTION_ASSIGNMENT:
    case NJS_TOKEN_MULTIPLICATION_ASSIGNMENT:
    case NJS_TOKEN_EXPONENTIATION_ASSIGNMENT:
    case NJS_TOKEN_DIVISION_ASSIGNMENT:
    case NJS_TOKEN_REMAINDER_ASSIGNMENT:
        return njs_generate_operation_assignment(vm, generator, node);

    case NJS_TOKEN_LOGICAL_OR_ASSIGNMENT:
    case NJS_TOKEN_LOGICAL_AND_ASSIGNMENT:
    case NJS_TOKEN_COALESCE_ASSIGNMENT:
        return njs_generate_logical_assignment(vm, generator, node);

    case NJS_TOKEN_BITWISE_OR:
    case NJS_TOKEN_BITWISE_XOR:
    case NJS_TOKEN_BITWISE_AND:
    case NJS_TOKEN_EQUAL:
    case NJS_TOKEN_NOT_EQUAL:
    case NJS_TOKEN_STRICT_EQUAL:
    case NJS_TOKEN_STRICT_NOT_EQUAL:
    case NJS_TOKEN_INSTANCEOF:
    case NJS_TOKEN_LESS:
    case NJS_TOKEN_LESS_OR_EQUAL:
    case NJS_TOKEN_GREATER:
    case NJS_TOKEN_GREATER_OR_EQUAL:
    case NJS_TOKEN_LEFT_SHIFT:
    case NJS_TOKEN_RIGHT_SHIFT:
    case NJS_TOKEN_UNSIGNED_RIGHT_SHIFT:
    case NJS_TOKEN_ADDITION:
    case NJS_TOKEN_SUBTRACTION:
    case NJS_TOKEN_MULTIPLICATION:
    case NJS_TOKEN_EXPONENTIATION:
    case NJS_TOKEN_DIVISION:
    case NJS_TOKEN_REMAINDER:
    case NJS_TOKEN_PROPERTY_DELETE:
    case NJS_TOKEN_PROPERTY:
    case NJS_TOKEN_PROPERTY_REF:
        return njs_generate_3addr_operation(vm, generator, node);

    case NJS_TOKEN_IN:
        /*
         * An "in" operation is parsed as standard binary expression
         * by njs_parser_binary_expression().  However, its operands
         * should be swapped to be uniform with other property operations
         * (get/set and delete) to use the property trap.
         */
        return njs_generate_3addr_operation(vm, generator, node);

    case NJS_TOKEN_LOGICAL_AND:
    case NJS_TOKEN_LOGICAL_OR:
    case NJS_TOKEN_COALESCE:
        return njs_generate_test_jump_expression(vm, generator, node);

    case NJS_TOKEN_OPTIONAL_CHAIN:
        return njs_generate_optional_chain(vm, generator, node);

    case NJS_TOKEN_DELETE:
    case NJS_TOKEN_VOID:
    case NJS_TOKEN_UNARY_PLUS:
    case NJS_TOKEN_UNARY_NEGATION:
    case NJS_TOKEN_LOGICAL_NOT:
    case NJS_TOKEN_BITWISE_NOT:
        return njs_generate_2addr_operation(vm, generator, node);

    case NJS_TOKEN_TYPEOF:
        return njs_generate_typeof_operation(vm, generator, node);

    case NJS_TOKEN_INCREMENT:
    case NJS_TOKEN_DECREMENT:
        return njs_generate_inc_dec_operation(vm, generator, node, 0);

    case NJS_TOKEN_POST_INCREMENT:
    case NJS_TOKEN_POST_DECREMENT:
        return njs_generate_inc_dec_operation(vm, generator, node, 1);

    case NJS_TOKEN_NULL:
    case NJS_TOKEN_TRUE:
    case NJS_TOKEN_FALSE:
    case NJS_TOKEN_NUMBER:
    case NJS_TOKEN_STRING:
        node->index = njs_scope_global_index(vm, &node->u.value,
                                             generator->runtime);
        if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        return njs_generator_stack_pop(vm, generator, NULL);

    case NJS_TOKEN_OBJECT_VALUE:
    case NJS_TOKEN_OPTIONAL_PRESERVE:
        node->index = njs_generate_wrapped_node(node)->index;
        return njs_generator_stack_pop(vm, generator, NULL);

    case NJS_TOKEN_OBJECT:
        return njs_generate_object(vm, generator, node);

    case NJS_TOKEN_PROPERTY_GETTER:
    case NJS_TOKEN_PROPERTY_SETTER:
        return njs_generate_property_accessor(vm, generator, node);

    case NJS_TOKEN_ARRAY:
        return njs_generate_array(vm, generator, node);

    case NJS_TOKEN_FUNCTION_EXPRESSION:
    case NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION:
        return njs_generate_function_expression(vm, generator, node);

    case NJS_TOKEN_FUNCTION:
    case NJS_TOKEN_ASYNC_FUNCTION:
        return njs_generate_function(vm, generator, node);

    case NJS_TOKEN_REGEXP:
        return njs_generate_regexp(vm, generator, node);

    case NJS_TOKEN_TEMPLATE_LITERAL:
        return njs_generate_template_literal(vm, generator, node);

    case NJS_TOKEN_EXTERNAL:
        return njs_generator_stack_pop(vm, generator, NULL);

    case NJS_TOKEN_NAME:
    case NJS_TOKEN_ARGUMENTS:
    case NJS_TOKEN_EVAL:
    case NJS_TOKEN_THIS:
        return njs_generate_name(vm, generator, node);

    case NJS_TOKEN_FUNCTION_DECLARATION:
    case NJS_TOKEN_ASYNC_FUNCTION_DECLARATION:
        return njs_generate_function_declaration(vm, generator, node);

    case NJS_TOKEN_FUNCTION_CALL:
        return njs_generate_function_call(vm, generator, node);

    case NJS_TOKEN_RETURN:
        return njs_generate_return_statement(vm, generator, node);

    case NJS_TOKEN_METHOD_CALL:
        return njs_generate_method_call(vm, generator, node);

    case NJS_TOKEN_TRY:
        return njs_generate_try_statement(vm, generator, node);

    case NJS_TOKEN_THROW:
        return njs_generate_throw_statement(vm, generator, node);

    case NJS_TOKEN_IMPORT:
        return njs_generate_import_statement(vm, generator, node);

    case NJS_TOKEN_EXPORT:
        return njs_generate_export_statement(vm, generator, node);

    case NJS_TOKEN_AWAIT:
        return njs_generate_await(vm, generator, node);

    default:
        njs_internal_error(vm, "Generator failed: unknown token");

        return NJS_ERROR;
    }
}


static njs_int_t
njs_generator_pop(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    return njs_generator_stack_pop(vm, generator, NULL);
}


static u_char *
njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator, size_t size)
{
    u_char  *p;

    if (generator->code_end + size <=
        generator->code_start + generator->code_size)
    {
        return generator->code_end;
    }

    size = njs_max(generator->code_end - generator->code_start + size,
                   generator->code_size);

    if (size < 1024) {
        size *= 2;

    } else {
        size += size / 2;
    }

    p = njs_mp_alloc(vm->mem_pool, size);
    if (njs_slow_path(p == NULL)) {
        njs_memory_error(vm);
        return NULL;
    }

    generator->code_size = size;

    size = generator->code_end - generator->code_start;
    memcpy(p, generator->code_start, size);

    njs_mp_free(vm->mem_pool, generator->code_start);

    generator->code_start = p;
    generator->code_end = p + size;

    return generator->code_end;
}


static njs_int_t
njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, u_char *code)
{
    njs_arr_t          *map;
    njs_vm_line_num_t  *last;

    map = generator->lines;

    if (map != NULL && node != NULL) {
        last = (map->items != 0) ? njs_arr_last(map) : NULL;
        if (last == NULL || (node->token_line != last->line)) {
            last = njs_arr_add(map);
            if (njs_slow_path(last == NULL)) {
                return NJS_ERROR;
            }

            last->line = node->token_line;
            last->offset = njs_code_offset(generator, code);
        }
    }

    return NJS_OK;
}


njs_vm_code_t *
njs_lookup_code(njs_vm_t *vm, u_char *pc)
{
    njs_uint_t     i;
    njs_vm_code_t  *code;

    code = vm->codes->start;

    for (i = 0; i < vm->codes->items; i++, code++) {
        if (code->start <= pc && pc < code->end) {
            return code;
        }
    }

    return NULL;
}


uint32_t
njs_lookup_line(njs_arr_t *lines, uint32_t offset)
{
    njs_uint_t         n;
    njs_vm_line_num_t  *map;

    n = 0;
    map = NULL;

    if (lines != NULL) {
        n = lines->items;
        map = (njs_vm_line_num_t *) lines->start;
    }

    while (n != 0) {
        if (offset >= map->offset && (n == 1 || offset < map[1].offset)) {
            return map->line;
        }

        map++;
        n--;
    }

    return 0;
}


static njs_int_t
njs_generate_name(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_variable_t         *var;
    njs_parser_scope_t     *scope;
    njs_vmcode_variable_t  *variable;

    var = njs_variable_reference(vm, node);
    if (njs_slow_path(var == NULL)) {
        ret = njs_generate_global_reference(vm, generator, node, 1);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        return njs_generator_stack_pop(vm, generator, NULL);
    }

    if (var->init) {
        return njs_generator_stack_pop(vm, generator, NULL);
    }

    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
        scope = njs_function_scope(node->scope);

        if (scope->dest_disable) {
            njs_generate_code(generator, njs_vmcode_variable_t, variable,
                              NJS_VMCODE_NOT_INITIALIZED, node);
            variable->dst = node->index;
        }
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
{
    njs_variable_t         *var;
    njs_parser_scope_t     *scope;
    njs_vmcode_variable_t  *variable;

    var = njs_variable_reference(vm, node);

    if (retvar != NULL) {
        *retvar = var;
    }

    if (njs_slow_path(var == NULL)) {
        switch (type) {
        case NJS_DECLARATION:
            return njs_generate_reference_error(vm, generator, node);

        case NJS_REFERENCE:
        case NJS_TYPEOF:
            return njs_generate_global_reference(vm, generator, node,
                                                 type == NJS_REFERENCE);
        }
    }

    if (var->init) {
        return NJS_OK;
    }

    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
        scope = njs_function_scope(node->scope);

        if ((!scope->dest_disable && njs_function_scope(var->scope) == scope)) {
            njs_generate_code(generator, njs_vmcode_variable_t, variable,
                              NJS_VMCODE_NOT_INITIALIZED, node);
            variable->dst = node->index;
        }
    }

    return NJS_OK;
}


static njs_int_t
njs_generate_variable_wo_dest(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
{
    njs_int_t           ret;
    njs_parser_scope_t  *scope;

    scope = njs_function_scope(node->scope);

    scope->dest_disable = 1;

    ret = njs_generate_variable(vm, generator, node, type, retvar);

    scope->dest_disable = 0;

    return ret;
}


static njs_int_t
njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_variable_t     *var;
    njs_parser_node_t  *lvalue, *expr;

    lvalue = node->left;

    ret = njs_generate_variable_wo_dest(vm, generator, lvalue,
                                        NJS_DECLARATION, &var);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    expr = node->right;

    if (expr == NULL) {
        /* Variable is only declared. */

        if (var->type == NJS_VARIABLE_CONST) {
            njs_syntax_error(vm, "missing initializer in const declaration");
            return NJS_ERROR;
        }

        if (var->type == NJS_VARIABLE_LET) {
            ret = njs_generate_let(vm, generator, node, var);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }
        }

        var->init = 1;

        return njs_generator_stack_pop(vm, generator, NULL);
    }

    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
        ret = njs_generate_wo_dest(vm, generator, expr);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

    } else {
        expr->dest = lvalue;

        njs_generator_next(generator, njs_generate, expr);
    }

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_var_statement_after, var, 0);
}


static njs_int_t
njs_generate_var_statement_after(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_string_t       name;
    njs_variable_t     *var;
    njs_parser_node_t  *lvalue, *expr;
    njs_vmcode_move_t  *move;

    lvalue = node->left;
    expr = node->right;
    var = generator->context;

    if (var->type == NJS_VARIABLE_LET || var->type == NJS_VARIABLE_CONST) {
        ret = njs_generate_let(vm, generator, node, var);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    var->init = 1;

    /*
     * lvalue and expression indexes are equal if the expression is an
     * empty object or expression result is stored directly in variable.
     */
    if (lvalue->index != expr->index) {
        njs_generate_code_move(generator, move, lvalue->index, expr->index,
                               lvalue);
    }

    node->index = expr->index;
    node->temporary = expr->temporary;

    if (expr->token_type == NJS_TOKEN_FUNCTION_EXPRESSION
         || expr->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION)
    {
        njs_string_get_unsafe(&expr->u.value.data.u.lambda->name, &name);

        if (name.length == 0) {
            njs_atom_to_value(vm, &expr->u.value.data.u.lambda->name,
                              node->left->u.reference.atom_id);
        }
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_let(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_variable_t *var)
{
    njs_vmcode_variable_t  *code;

    njs_generate_code(generator, njs_vmcode_variable_t, code,
                      NJS_VMCODE_LET, node);
    code->dst = var->index;

    return NJS_OK;
}


static njs_int_t
njs_generate_if_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_BLOCK,
                                    &node->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* The condition expression. */

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_if_statement_cond, NULL, 0);
}


static njs_int_t
njs_generate_if_statement_cond(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t               ret;
    njs_jump_off_t          jump_offset;
    njs_vmcode_cond_jump_t  *cond_jump;

    njs_generate_code(generator, njs_vmcode_cond_jump_t, cond_jump,
                      NJS_VMCODE_IF_FALSE_JUMP, node);
    cond_jump->cond = node->left->index;

    ret = njs_generate_node_index_release(vm, generator, node->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    jump_offset = njs_code_offset(generator, cond_jump);

    if (node->right != NULL && node->right->token_type == NJS_TOKEN_BRANCHING) {

        /* The "then" branch in a case of "if/then/else" statement. */

        node = node->right;

        njs_generator_next(generator, njs_generate, node->left);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_if_statement_then,
                                   &jump_offset, sizeof(njs_jump_off_t));
    }

    /*
     * The "then" branch in a case of "if/then" statement
     * or the "else" branch in a case of "if/then/else" statement.
     */

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_if_statement_else,
                               &jump_offset, sizeof(njs_jump_off_t));
}


static njs_int_t
njs_generate_if_statement_then(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_jump_off_t     *jump_offset, label_offset;
    njs_vmcode_jump_t  *jump;

    ret = njs_generate_node_index_release(vm, generator, node->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    jump_offset = (njs_jump_off_t *) generator->context;
    label_offset = *jump_offset + offsetof(njs_vmcode_cond_jump_t, offset);

    njs_generate_code_jump(generator, jump, 0);
    njs_code_set_offset(generator, label_offset, *jump_offset);

    *jump_offset = njs_code_offset(generator, jump);

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_if_statement_else,
                               jump_offset, 0);
}


static njs_int_t
njs_generate_if_statement_else(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t       ret;
    njs_jump_off_t  *jump_offset, label_offset;

    jump_offset = (njs_jump_off_t *) generator->context;
    label_offset = *jump_offset + offsetof(njs_vmcode_cond_jump_t, offset);

    ret = njs_generate_node_index_release(vm, generator, node->right);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_code_set_offset(generator, label_offset, *jump_offset);

    njs_generate_patch_block_exit(vm, generator);

    return njs_generator_stack_pop(vm, generator, generator->context);
}


static njs_int_t
njs_generate_cond_expression(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_cond_expression_handler, NULL, 0);
}


static njs_int_t
njs_generate_cond_expression_handler(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_jump_off_t          jump_offset;
    njs_vmcode_cond_jump_t  *cond_jump;

    njs_generate_code(generator, njs_vmcode_cond_jump_t, cond_jump,
                      NJS_VMCODE_IF_FALSE_JUMP, node);

    jump_offset = njs_code_offset(generator, cond_jump);
    cond_jump->cond = node->left->index;

    node->index = njs_generate_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generator_next(generator, njs_generate, node->right->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_cond_expression_true,
                               &jump_offset, sizeof(njs_jump_off_t));
}


static njs_int_t
njs_generate_cond_expression_true(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_jump_off_t     jump_offset;
    njs_parser_node_t  *branch;
    njs_vmcode_move_t  *move;
    njs_vmcode_jump_t  *jump;

    branch = node->right;

    /*
     * Branches usually uses node->index as destination, however,
     * if branch expression is a literal, variable or assignment,
     * then a MOVE operation is required.
     */

    if (node->index != branch->left->index) {
        njs_generate_code_move(generator, move, node->index,
                               branch->left->index, node);
    }

    ret = njs_generate_node_index_release(vm, generator, branch->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_code_jump(generator, jump, 0);
    jump_offset = njs_code_offset(generator, jump);

    njs_code_set_jump_offset(generator, njs_vmcode_cond_jump_t,
                             *((njs_jump_off_t *) generator->context));

    njs_generator_next(generator, njs_generate, branch->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_cond_expression_false,
                               &jump_offset, sizeof(njs_jump_off_t));
}


static njs_int_t
njs_generate_cond_expression_false(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *branch;
    njs_vmcode_move_t  *move;

    branch = node->right;

    if (node->index != branch->right->index) {
        njs_generate_code_move(generator, move, node->index,
                               branch->right->index, node);
    }

    njs_code_set_jump_offset(generator, njs_vmcode_cond_jump_t,
                             *((njs_jump_off_t *) generator->context));

    ret = njs_generate_node_index_release(vm, generator, branch->right);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, generator->context);
}


static njs_int_t
njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *swtch)
{
    njs_generator_switch_ctx_t  ctx;

    /* The "switch" expression. */

    njs_generator_next(generator, njs_generate, swtch->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), swtch,
                               njs_generate_switch_expression,
                               &ctx, sizeof(njs_generator_switch_ctx_t));
}


static njs_int_t
njs_generate_switch_expression(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *swtch)
{
    njs_int_t                   ret;
    njs_parser_node_t           *expr;
    njs_vmcode_move_t           *move;
    njs_generator_switch_ctx_t  *ctx;

    ctx = generator->context;

    expr = swtch->left;
    ctx->index = expr->index;

    if (!expr->temporary) {
        ctx->index = njs_generate_temp_index_get(vm, generator, swtch);
        if (njs_slow_path(ctx->index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        njs_generate_code_move(generator, move, ctx->index, expr->index, swtch);
    }

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_SWITCH,
                                   &swtch->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ctx->patch = NULL;
    ctx->last = &ctx->patch;

    if (swtch->right != NULL) {

        /* The "case" expression. */

        njs_generator_next(generator, njs_generate_switch_case, swtch->right);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), swtch,
                                   njs_generate_switch_case_end, ctx, 0);
    }

    return njs_generate_switch_case_end(vm, generator, swtch);
}


static njs_int_t
njs_generate_switch_case(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *branch)
{
    if (branch->token_type == NJS_TOKEN_DEFAULT) {
        if (branch->left == NULL) {
            return njs_generator_stack_pop(vm, generator, NULL);
        }

        branch = branch->left;
    }

    njs_generator_next(generator, njs_generate, branch->right->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), branch,
                               njs_generate_switch_case_after,
                               generator->context, 0);
}


static njs_int_t
njs_generate_switch_case_after(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *branch)
{
    njs_int_t                   ret;
    njs_parser_node_t           *node;
    njs_generator_patch_t       *patch;
    njs_vmcode_equal_jump_t     *equal;
    njs_generator_switch_ctx_t  *ctx;

    ctx = generator->context;
    node = branch->right;

    njs_generate_code(generator, njs_vmcode_equal_jump_t, equal,
                      NJS_VMCODE_IF_EQUAL_JUMP, branch);
    equal->offset = offsetof(njs_vmcode_equal_jump_t, offset);
    equal->value1 = ctx->index;
    equal->value2 = node->left->index;

    ret = njs_generate_node_index_release(vm, generator, node->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    patch = njs_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
    if (njs_slow_path(patch == NULL)) {
        return NJS_ERROR;
    }

    patch->jump_offset = njs_code_offset(generator, equal)
                         + offsetof(njs_vmcode_equal_jump_t, offset);
    patch->label = no_label;

    *ctx->last = patch;
    ctx->last = &patch->next;

    if (branch->left == NULL) {
        return njs_generator_stack_pop(vm, generator, NULL);
    }

    branch = branch->left;

    if (branch->token_type == NJS_TOKEN_DEFAULT) {
        branch = branch->left;

        if (branch == NULL) {
            return njs_generator_stack_pop(vm, generator, NULL);
        }
    }

    njs_generator_next(generator, njs_generate, branch->right->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), branch,
                               njs_generate_switch_case_after, ctx, 0);
}

static njs_int_t
njs_generate_switch_case_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *swtch)
{
    njs_int_t                   ret;
    njs_parser_node_t           *branch;
    njs_generator_switch_ctx_t  *ctx;

    ctx = generator->context;

    /* Release either temporary index or temporary expr->index. */
    ret = njs_generate_index_release(vm, generator, ctx->index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_code_jump(generator, ctx->jump,
                           offsetof(njs_vmcode_jump_t, offset));

    ctx->jump_offset = njs_code_offset(generator, ctx->jump);

    branch = swtch->right;

    if (branch != NULL) {
        njs_generator_next(generator, njs_generate_switch_default, branch);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), swtch,
                                   njs_generate_switch_end, ctx, 0);
    }

    return njs_generate_switch_end(vm, generator, swtch);
}


static njs_int_t
njs_generate_switch_default(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *branch)
{
    njs_parser_node_t           *node;
    njs_generator_patch_t       *next;
    njs_generator_switch_ctx_t  *ctx;

    ctx = generator->context;

    if (branch->token_type == NJS_TOKEN_DEFAULT) {
        njs_code_set_jump_offset(generator, njs_vmcode_jump_t,
                                 ctx->jump_offset);
        ctx->jump = NULL;
        node = branch;

    } else {
        njs_code_update_offset(generator, ctx->patch);

        next = ctx->patch->next;

        njs_mp_free(vm->mem_pool, ctx->patch);

        ctx->patch = next;
        node = branch->right;
    }

    njs_generator_next(generator, njs_generate, node->right);

    branch = branch->left;

    if (branch == NULL) {
        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), branch,
                                   njs_generator_pop, NULL, 0);
    }

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), branch,
                               njs_generate_switch_default, ctx, 0);
}


static njs_int_t
njs_generate_switch_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *swtch)
{
    njs_generator_switch_ctx_t  *ctx;

    ctx = generator->context;

    if (ctx->jump != NULL) {
        /* A "switch" without default case. */
        njs_code_set_jump_offset(generator, njs_vmcode_jump_t,
                                 ctx->jump_offset);
    }

    /* Patch "break" statements offsets. */
    njs_generate_patch_block_exit(vm, generator);

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_while_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_vmcode_jump_t         *jump;
    njs_generator_loop_ctx_t  ctx;

    /*
     * Set a jump to the loop condition.  This jump is executed once just on
     * the loop enter and eliminates execution of one additional jump inside
     * the loop per each iteration.
     */

    njs_generate_code_jump(generator, jump, 0);
    ctx.jump_offset = njs_code_offset(generator, jump);

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
                                   &node->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ctx.loop_offset = njs_code_offset(generator, generator->code_end);

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_while_condition,
                               &ctx, sizeof(njs_generator_loop_ctx_t));
}


static njs_int_t
njs_generate_while_condition(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_code_set_jump_offset(generator, njs_vmcode_jump_t, ctx->jump_offset);

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_while_end, ctx, 0);
}


static njs_int_t
njs_generate_while_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_vmcode_cond_jump_t    *cond_jump;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    njs_generate_code(generator, njs_vmcode_cond_jump_t, cond_jump,
                      NJS_VMCODE_IF_TRUE_JUMP, node->right);
    cond_jump->offset = ctx->loop_offset - njs_code_offset(generator,
                                                           cond_jump);
    cond_jump->cond = node->right->index;

    njs_generate_patch_block_exit(vm, generator);

    ret = njs_generate_node_index_release(vm, generator, node->right);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_do_while_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_generator_loop_ctx_t  ctx;

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
                                   &node->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ctx.loop_offset = njs_code_offset(generator, generator->code_end);

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_do_while_condition,
                               &ctx, sizeof(njs_generator_loop_ctx_t));
}


static njs_int_t
njs_generate_do_while_condition(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_do_while_end,
                               generator->context, 0);
}


static njs_int_t
njs_generate_do_while_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_vmcode_cond_jump_t    *cond_jump;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    njs_generate_code(generator, njs_vmcode_cond_jump_t, cond_jump,
                      NJS_VMCODE_IF_TRUE_JUMP, node->right);
    cond_jump->offset = ctx->loop_offset
                        - njs_code_offset(generator, cond_jump);
    cond_jump->cond = node->right->index;

    njs_generate_patch_block_exit(vm, generator);

    ret = njs_generate_node_index_release(vm, generator, node->right);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_generator_loop_ctx_t  ctx;

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
                                   &node->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ctx.jump = NULL;

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_init,
                               &ctx, sizeof(njs_generator_loop_ctx_t));
}


static njs_int_t
njs_generate_for_init(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t         *condition;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    ret = njs_generate_node_index_release(vm, generator, node->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    condition = node->right->left;

    /*
     * Closures can occur in conditional and loop updates.  This must be
     * foreseen in order to generate optimized code for let updates.
     */

    ret = njs_generate_for_resolve_closure(vm, condition);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ctx->jump_offset = 0;

    if (condition != NULL) {
        /*
         * The loop condition presents so set a jump to it.  This jump is
         * executed once just after the loop initialization and eliminates
         * execution of one additional jump inside the loop per each iteration.
         */
        njs_generate_code_jump(generator, ctx->jump, 0);
        ctx->jump_offset = njs_code_offset(generator, ctx->jump);
    }

    /* The loop body. */

    ctx->loop_offset = njs_code_offset(generator, generator->code_end);

    njs_generator_next(generator, njs_generate, node->right->right->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_body, ctx, 0);
}


static njs_int_t
njs_generate_for_body(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t         *update, *init;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    /* The loop update. */

    init = node->left;
    update = node->right->right->right;

    ret = njs_generate_for_resolve_closure(vm, update);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_for_let_update(vm, generator, init);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_generator_next(generator, njs_generate, update);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_update, ctx, 0);
}


static njs_int_t
njs_generate_for_update(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t         *condition, *update;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    update = node->right->right->right;
    condition = node->right->left;

    ret = njs_generate_node_index_release(vm, generator, update);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* The loop condition. */

    if (condition != NULL) {
        njs_code_set_jump_offset(generator, njs_vmcode_jump_t,
                                 ctx->jump_offset);

        njs_generator_next(generator, njs_generate, condition);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_for_end, ctx, 0);
    }

    return njs_generate_for_end(vm, generator, node);
}


static njs_int_t
njs_generate_for_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t         *condition;
    njs_vmcode_cond_jump_t    *cond_jump;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    condition = node->right->left;

    if (condition != NULL) {
        njs_generate_code(generator, njs_vmcode_cond_jump_t, cond_jump,
                          NJS_VMCODE_IF_TRUE_JUMP, condition);
        cond_jump->offset = ctx->loop_offset
                            - njs_code_offset(generator, cond_jump);
        cond_jump->cond = condition->index;

        njs_generate_patch_block_exit(vm, generator);

        ret = njs_generate_node_index_release(vm, generator, condition);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        return njs_generator_stack_pop(vm, generator, ctx);
    }

    njs_generate_code_jump(generator, ctx->jump,
                           ctx->loop_offset - njs_code_offset(generator,
                                                              ctx->jump));

    njs_generate_patch_block_exit(vm, generator);

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_for_let_update(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_node_t         *let;
    njs_vmcode_variable_t     *code_var;
    njs_variable_reference_t  *ref;

    while (node != NULL && node->token_type == NJS_TOKEN_STATEMENT) {
        let = node->right;

        if (let->token_type != NJS_TOKEN_LET
            && let->token_type != NJS_TOKEN_CONST)
        {
            return NJS_OK;
        }

        ref = &let->left->u.reference;

        if (ref->variable->closure) {
            njs_generate_code(generator, njs_vmcode_variable_t, code_var,
                              NJS_VMCODE_LET_UPDATE, let);
            code_var->dst = let->left->index;
        }

        node = node->left;
    }

    return NJS_OK;
}


static njs_int_t
njs_generate_for_resolve_closure_cb(njs_vm_t *vm, njs_parser_node_t *node,
    void *unused)
{
    njs_bool_t      closure;
    njs_variable_t  *var;

    if (node->token_type == NJS_TOKEN_NAME) {
        var = njs_variable_resolve(vm, node);

        if (njs_fast_path(var != NULL)) {
            closure = njs_variable_closure_test(node->scope, var->scope);

            if (closure) {
                var->closure = 1;
            }
        }
    }

    return NJS_OK;
}


static njs_int_t
njs_generate_for_resolve_closure(njs_vm_t *vm, njs_parser_node_t *node)
{
    return njs_parser_traverse(vm, node, NULL,
                               njs_generate_for_resolve_closure_cb);
}


static njs_int_t
njs_generate_for_in_name_assign(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_variable_t            *var;
    njs_parser_node_t         *foreach, *lvalue, *expr;
    njs_vmcode_move_t         *move;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    foreach = node->left;
    lvalue = foreach->left;
    expr = node->right;

    var = njs_variable_reference(vm, lvalue);

    if (var != NULL) {
        ctx->index_next_value = lvalue->index;

    } else {
        ctx->index_next_value = njs_generate_temp_index_get(vm, generator,
                                                            foreach->left);
        if (njs_slow_path(ctx->index_next_value == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        if (expr != NULL) {
            expr->index = ctx->index_next_value;

            /*
             * lvalue and expression indexes are equal if the expression is an
             * empty object or expression result is stored directly in variable.
             */
            if (lvalue->index != expr->index) {
                njs_generate_code_move(generator, move, lvalue->index,
                                       expr->index, expr);
            }

            ret = njs_generate_global_property_set(vm, generator, foreach->left,
                                                   expr);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }
        }
    }
    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_for_in_body_wo_decl(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_jump_off_t            prop_offset;
    njs_parser_node_t         *foreach, *name;
    njs_vmcode_prop_next_t    *prop_next;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    foreach = node->left;
    name = foreach->left->right;

    /* The loop iterator. */

    if (name != NULL) {
        ret = njs_generate_for_let_update(vm, generator, foreach->left);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t,
                             ctx->jump_offset);

    njs_generate_code(generator, njs_vmcode_prop_next_t, prop_next,
                      NJS_VMCODE_PROPERTY_NEXT, node->left->left);
    prop_offset = njs_code_offset(generator, prop_next);
    prop_next->retval = ctx->index_next_value;
    prop_next->object = foreach->right->index;
    prop_next->next = ctx->index;
    prop_next->offset = ctx->loop_offset - prop_offset;

    njs_generate_patch_block_exit(vm, generator);

    /*
     * Release object and iterator indexes: an object can be a function result
     * or a property of another object and an iterator can be given with "let".
     */
    ret = njs_generate_children_indexes_release(vm, generator, foreach);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_index_release(vm, generator, ctx->index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_for_in_object_wo_decl(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                  ret;
    njs_parser_node_t          *foreach, *name;
    njs_generator_loop_ctx_t   *ctx;
    njs_vmcode_prop_foreach_t  *prop_foreach;

    ctx = generator->context;

    foreach = node->left;
    name = foreach->left->right;

    if (name != NULL) {
        ctx->var->init = 1;
    }

    njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
                      NJS_VMCODE_PROPERTY_FOREACH, foreach);
    ctx->jump_offset = njs_code_offset(generator, prop_foreach);
    prop_foreach->object = foreach->right->index;

    ctx->index = njs_generate_temp_index_get(vm, generator, foreach->right);
    if (njs_slow_path(ctx->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    prop_foreach->next = ctx->index;

    /* The loop body. */

    ctx->loop_offset = njs_code_offset(generator, generator->code_end);


    /* 1) left. */

    njs_generator_next(generator, njs_generate, foreach->left);

    /* 4) loop-body-end. */

    ret = njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_in_body_wo_decl, ctx, 0);
    if (ret != NJS_OK) {
        return ret;
    }

    /* 3) loop-body. */

    ret = njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node->right,
                               njs_generate, ctx, 0);
    if (ret != NJS_OK) {
        return ret;
    }

    /* 2) assign value to name. */

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_in_name_assign, ctx, 0);

}


static njs_int_t
njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t         *foreach, *name;
    njs_generator_loop_ctx_t  ctx;

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
                                   &node->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* The object. */

    foreach = node->left;

    if (!njs_generate_is_property_lvalue(foreach->left)) {
        name = foreach->left->right;

        if (name != NULL) {
            name = name->left;

            ret = njs_generate_variable_wo_dest(vm, generator, name,
                                            NJS_DECLARATION, &ctx.var);
            if (njs_slow_path(ret != NJS_OK)) {
                return NJS_ERROR;
            }

            foreach->left->index = name->index;

            njs_generator_next(generator, njs_generate, foreach->right);

            return njs_generator_after(vm, generator,
                                       njs_queue_first(&generator->stack), node,
                                       njs_generate_for_in_object,
                                       &ctx, sizeof(njs_generator_loop_ctx_t));
        }

    } else {

        /* foreach->right is object in 'in object'. */

        njs_generator_next(generator, njs_generate, foreach->right);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_for_in_object_left_hand_expr,
                                   &ctx, sizeof(njs_generator_loop_ctx_t));

    }

    njs_generator_next(generator, njs_generate, foreach->right);

    return  njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_for_in_object_wo_decl,
                              &ctx, sizeof(njs_generator_loop_ctx_t));
}


static njs_int_t
njs_generate_for_in_object_left_hand_expr(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_parser_node_t          *foreach;
    njs_generator_loop_ctx_t   *ctx;
    njs_vmcode_prop_foreach_t  *prop_foreach;

    ctx = generator->context;

    foreach = node->left;

    njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
                      NJS_VMCODE_PROPERTY_FOREACH, foreach);
    ctx->jump_offset = njs_code_offset(generator, prop_foreach);
    prop_foreach->object = foreach->right->index;

    ctx->index = njs_generate_temp_index_get(vm, generator, foreach->right);
    if (njs_slow_path(ctx->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    ctx->index_next_value = njs_generate_temp_index_get(vm, generator,
                                                        foreach->left);
    if (njs_slow_path(ctx->index_next_value == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    prop_foreach->next = ctx->index;

    ctx->loop_offset = njs_code_offset(generator, generator->code_end);

    /* Object part calculation. */

    njs_generator_next(generator, njs_generate, foreach->left->left);

    /* The loop body. */

    ret = njs_generator_after(vm, generator, njs_queue_first(&generator->stack),
                              node, njs_generate_for_in_body_left_hand_expr,
                              ctx, sizeof(njs_generator_loop_ctx_t));
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* set-property and block. */

    ret = njs_generator_after(vm, generator, njs_queue_first(&generator->stack),
                              node, njs_generate_for_in_set_prop_block, ctx,
                              sizeof(njs_generator_loop_ctx_t));
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* Key part calculation. */

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               foreach->left->right, njs_generate, NULL, 0);
}


static njs_int_t
njs_generate_for_in_set_prop_block(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_t              opcode;
    njs_parser_node_t         *foreach, *prop;
    njs_vmcode_prop_set_t     *prop_set;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    foreach = node->left;
    prop = foreach->left->right;

    if (prop->token_type == NJS_TOKEN_STRING
        || (prop->token_type == NJS_TOKEN_NUMBER
            && prop->u.value.atom_id != NJS_ATOM_STRING_unknown))
    {
        opcode = NJS_VMCODE_PROPERTY_ATOM_SET;

    } else {
        opcode = NJS_VMCODE_PROPERTY_SET;
    }

    njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
                      opcode, foreach);
    prop_set->object = foreach->left->left->index;
    prop_set->property = prop->index;
    prop_set->value = ctx->index_next_value;

    njs_generator_next(generator, njs_generate, node->right);

    return NJS_OK;
}


static njs_int_t
njs_generate_for_in_object(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_node_t          *foreach, *name;
    njs_generator_loop_ctx_t   *ctx;
    njs_vmcode_prop_foreach_t  *prop_foreach;

    ctx = generator->context;

    foreach = node->left;
    name = foreach->left->right;

    if (name != NULL) {
        ctx->var->init = 1;
    }

    njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
                      NJS_VMCODE_PROPERTY_FOREACH, foreach);
    ctx->jump_offset = njs_code_offset(generator, prop_foreach);
    prop_foreach->object = foreach->right->index;

    ctx->index = njs_generate_temp_index_get(vm, generator, foreach->right);
    if (njs_slow_path(ctx->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    prop_foreach->next = ctx->index;

    /* The loop body. */

    ctx->loop_offset = njs_code_offset(generator, generator->code_end);

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_for_in_body, ctx, 0);
}


static njs_int_t
njs_generate_for_in_body_left_hand_expr(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_jump_off_t            prop_offset;
    njs_parser_node_t         *foreach;
    njs_vmcode_prop_next_t    *prop_next;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    foreach = node->left;

    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t,
                             ctx->jump_offset);

    njs_generate_code(generator, njs_vmcode_prop_next_t, prop_next,
                      NJS_VMCODE_PROPERTY_NEXT, node->left->left);
    prop_offset = njs_code_offset(generator, prop_next);
    prop_next->retval = ctx->index_next_value;
    prop_next->object = foreach->right->index;
    prop_next->next = ctx->index;
    prop_next->offset = ctx->loop_offset - prop_offset;

    njs_generate_patch_block_exit(vm, generator);

    /*
     * Release object and iterator indexes: an object can be a function result
     * or a property of another object and an iterator can be given with "let".
     */
    ret = njs_generate_children_indexes_release(vm, generator, foreach);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_index_release(vm, generator, ctx->index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_for_in_body(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                 ret;
    njs_jump_off_t            prop_offset;
    njs_parser_node_t         *foreach, *name;
    njs_vmcode_prop_next_t    *prop_next;
    njs_generator_loop_ctx_t  *ctx;

    ctx = generator->context;

    foreach = node->left;
    name = foreach->left->right;

    /* The loop iterator. */

    if (name != NULL) {
        ret = njs_generate_for_let_update(vm, generator, foreach->left);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    njs_generate_patch_block(vm, generator, generator->block,
                             NJS_GENERATOR_CONTINUATION);

    njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t,
                             ctx->jump_offset);

    njs_generate_code(generator, njs_vmcode_prop_next_t, prop_next,
                      NJS_VMCODE_PROPERTY_NEXT, node->left->left);
    prop_offset = njs_code_offset(generator, prop_next);
    prop_next->retval = foreach->left->index;
    prop_next->object = foreach->right->index;
    prop_next->next = ctx->index;
    prop_next->offset = ctx->loop_offset - prop_offset;

    njs_generate_patch_block_exit(vm, generator);

    /*
     * Release object and iterator indexes: an object can be a function result
     * or a property of another object and an iterator can be given with "let".
     */
    ret = njs_generate_children_indexes_release(vm, generator, foreach);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_index_release(vm, generator, ctx->index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


#ifdef NJS_DEBUG_GENERATOR
njs_inline const char*
njs_block_type(njs_generator_block_type_t type)
{
    switch (type) {
    case NJS_GENERATOR_LOOP:
        return "LOOP  ";
    case NJS_GENERATOR_SWITCH:
        return "SWITCH";
    case NJS_GENERATOR_BLOCK:
        return "BLOCK ";
    default:
        return "TRY   ";
    }
}
#endif


static njs_int_t
njs_generate_start_block(njs_vm_t *vm, njs_generator_t *generator,
    njs_generator_block_type_t type, const njs_str_t *label)
{
    njs_generator_block_t  *block;

    block = njs_mp_alloc(vm->mem_pool, sizeof(njs_generator_block_t));

    if (njs_fast_path(block != NULL)) {
        block->next = generator->block;
        generator->block = block;

        block->type = type;
        block->label = *label;
        block->continuation = NULL;
        block->exit = NULL;

        block->index = 0;

        njs_debug_generator(vm, "START %s %p", njs_block_type(type), block);

        return NJS_OK;
    }

    return NJS_ERROR;
}


static njs_generator_block_t *
njs_generate_lookup_block(njs_generator_block_t *block, uint32_t mask,
    const njs_str_t *label)
{
    if (njs_strstr_eq(label, &return_label)) {
        mask = NJS_GENERATOR_TRY;
        label = &no_label;
    }

    while (block != NULL) {
        if ((block->type & mask) != 0
            && (label->length == 0 || njs_strstr_eq(&block->label, label)))
        {
            return block;
        }

        block = block->next;
    }

    return NULL;
}


static njs_generator_block_t *
njs_generate_find_block(njs_vm_t *vm, njs_generator_block_t *block,
    uint32_t mask, const njs_str_t *label)
{
    njs_generator_block_t  *dest_block;

    /*
     * ES5.1: 12.8 The break Statement
     * "break" without a label is valid only from within
     * loop or switch statement.
     */
    if ((mask & NJS_GENERATOR_ALL) == NJS_GENERATOR_ALL
        && label->length != 0)
    {
        mask |= NJS_GENERATOR_BLOCK;
    }

    dest_block = njs_generate_lookup_block(block, mask, label);

    if (dest_block != NULL) {

        /*
         * Looking for intermediate try-catch blocks. Before jumping to
         * the destination finally blocks have to be executed.
         */

        while (block != NULL) {
            if (block->type & NJS_GENERATOR_TRY) {
                njs_debug_generator(vm, "FIND  %s %p",
                                    njs_block_type(block->type), block);
                return block;
            }

            if (block == dest_block) {
                break;
            }

            block = block->next;
        }
    }

    njs_debug_generator(vm, "FIND  %s %p",
                        dest_block != NULL ? njs_block_type(dest_block->type)
                                           : "NONE   ",
                        dest_block);

    return dest_block;
}


static njs_generator_patch_t *
njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_block_t *block,
    const njs_str_t *label, njs_jump_off_t offset)
{
    njs_generator_patch_t  *patch;

    patch = njs_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
    if (njs_slow_path(patch == NULL)) {
        njs_memory_error(vm);
        return NULL;
    }

    patch->next = block->continuation;
    block->continuation = patch;

    patch->jump_offset = offset;

    patch->label = *label;


    njs_debug_generator(vm, "MAKE  CONT   %p %L %V", patch, patch->jump_offset,
                        &patch->label);

    return patch;
}


static void
njs_generate_patch(njs_vm_t *vm, njs_generator_t *generator,
    njs_generator_patch_t *list)
{
    njs_generator_patch_t  *patch, *next;

    for (patch = list; patch != NULL; patch = next) {
        njs_code_update_offset(generator, patch);
        njs_debug_generator(vm, "PATCH        %p at %L to %L %V", patch,
                            patch->jump_offset,
                            *(njs_code_jump_ptr(generator, patch->jump_offset)),
                            &patch->label);
        next = patch->next;

        njs_mp_free(vm->mem_pool, patch);
    }
}


static void
njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator,
    njs_generator_block_t *block, unsigned type)
{
    if (type & NJS_GENERATOR_CONTINUATION) {
        njs_debug_generator(vm, "PATCH CONT   %p", block);
        njs_generate_patch(vm, generator, block->continuation);
    }

    if (type & NJS_GENERATOR_EXIT) {
        njs_debug_generator(vm, "PATCH EXIT   %p", block);
        njs_generate_patch(vm, generator, block->exit);
    }
}


static njs_generator_patch_t *
njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_block_t *block,
    const njs_str_t *label, njs_jump_off_t offset)
{
    njs_generator_patch_t  *patch;

    patch = njs_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
    if (njs_slow_path(patch == NULL)) {
        njs_memory_error(vm);
        return NULL;
    }

    patch->next = block->exit;
    block->exit = patch;

    patch->jump_offset = offset;

    patch->label = *label;

    njs_debug_generator(vm, "MAKE  EXIT   %p %L %V", patch, patch->jump_offset,
                        &patch->label);

    return patch;
}


static void
njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator)
{
    njs_generator_block_t  *block;

    block = generator->block;
    generator->block = block->next;

    njs_generate_patch_block(vm, generator, block, NJS_GENERATOR_EXIT);

    njs_debug_generator(vm, "EXIT  %s %p", njs_block_type(block->type), block);

    njs_mp_free(vm->mem_pool, block);
}


/*
 * TODO: support multiple destination points from within try-catch block.
 */
static const njs_str_t *
njs_generate_jump_destination(njs_vm_t *vm, njs_generator_block_t *block,
    const char *inst_type, uint32_t mask, const njs_str_t *label1,
    const njs_str_t *label2)
{
    njs_generator_block_t  *block1, *block2;

    if (label1->length == undef_label.length) {
        return label2;
    }

    if (label2->length == undef_label.length) {
        return label1;
    }

    block1 = njs_generate_lookup_block(block, mask, label1);
    block2 = njs_generate_lookup_block(block, mask, label2);

    if (block1 != block2) {
        njs_internal_error(vm, "%s instructions with different labels "
                           "(\"%V\" vs \"%V\") "
                           "from try-catch block are not supported", inst_type,
                            label1, label2);

        return NULL;
    }

    return label1;
}


static njs_int_t
njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    const njs_str_t        *label, *dest;
    njs_vmcode_jump_t      *jump;
    njs_generator_patch_t  *patch;
    njs_generator_block_t  *block;

    label = &node->name;

    block = njs_generate_find_block(vm, generator->block, NJS_GENERATOR_LOOP,
                                    label);

    if (njs_slow_path(block == NULL)) {
        goto syntax_error;
    }

    if (block->type == NJS_GENERATOR_TRY && block->continuation != NULL) {
        dest = njs_generate_jump_destination(vm, block->next, "continue",
                                             NJS_GENERATOR_LOOP,
                                             &block->continuation->label,
                                             label);
        if (njs_slow_path(dest == NULL)) {
            return NJS_ERROR;
        }
    }

    njs_generate_code_jump(generator, jump,
                           offsetof(njs_vmcode_jump_t, offset));

    patch = njs_generate_make_continuation_patch(vm, block, label,
                                         njs_code_offset(generator, jump)
                                         + offsetof(njs_vmcode_jump_t, offset));
    if (njs_slow_path(patch == NULL)) {
        return NJS_ERROR;
    }

    return njs_generator_stack_pop(vm, generator, NULL);

syntax_error:

    njs_generate_syntax_error(vm, node, &generator->file,
                              "Illegal continue statement");

    return NJS_ERROR;
}


static njs_int_t
njs_generate_break_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    const njs_str_t        *label, *dest;
    njs_vmcode_jump_t      *jump;
    njs_generator_patch_t  *patch;
    njs_generator_block_t  *block;

    label = &node->name;

    block = njs_generate_find_block(vm, generator->block, NJS_GENERATOR_ALL,
                                    label);
    if (njs_slow_path(block == NULL)) {
        goto syntax_error;
     }

    if (block->type == NJS_GENERATOR_TRY && block->exit != NULL) {
        dest = njs_generate_jump_destination(vm, block->next, "break/return",
                                             NJS_GENERATOR_ALL,
                                             &block->exit->label, label);
        if (njs_slow_path(dest == NULL)) {
            return NJS_ERROR;
        }
    }

    njs_generate_code_jump(generator, jump,
                           offsetof(njs_vmcode_jump_t, offset));

    patch = njs_generate_make_exit_patch(vm, block, label,
                                         njs_code_offset(generator, jump)
                                         + offsetof(njs_vmcode_jump_t, offset));
    if (njs_slow_path(patch == NULL)) {
        return NJS_ERROR;
    }

    return njs_generator_stack_pop(vm, generator, NULL);

syntax_error:

    njs_generate_syntax_error(vm, node, &generator->file,
                              "Illegal break statement");

    return NJS_ERROR;
}


static njs_int_t
njs_generate_debugger_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_debugger_t  *debugger;

    njs_generate_code(generator, njs_vmcode_debugger_t, debugger,
                      NJS_VMCODE_DEBUGGER, node);

    debugger->retval = njs_generate_dest_index(vm, generator, node);
    if (njs_slow_path(debugger->retval == NJS_INDEX_ERROR)) {
        return debugger->retval;
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_variable_t         *var;
    njs_queue_link_t       *link;
    njs_parser_node_t      *right;
    njs_vmcode_variable_t  *code;

    right = node->right;

    if (right != NULL && right->token_type == NJS_TOKEN_NAME) {
        var = njs_variable_reference(vm, right);
        if (njs_slow_path(var == NULL)) {
            goto statement;
        }

        if (!var->init && (var->type == NJS_VARIABLE_LET
            || var->type == NJS_VARIABLE_CONST))
        {
            njs_generate_code(generator, njs_vmcode_variable_t, code,
                              NJS_VMCODE_INITIALIZATION_TEST, right);
            code->dst = right->index;
        }

        if (node->left == NULL) {
            return njs_generator_stack_pop(vm, generator, NULL);
        }

        node = node->left;
    }

statement:

    link = njs_queue_first(&generator->stack);

    ret = njs_generate_children(vm, generator, node);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator, link, right,
                               njs_generate_node_index_release_pop, NULL, 0);
}

static njs_int_t
njs_generate_block_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t         ret;
    njs_queue_link_t  *link;

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_BLOCK,
                                   &node->name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    link = njs_queue_first(&generator->stack);

    ret = njs_generate_statement(vm, generator, node);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator, link, node,
                               njs_generate_block_statement_end, NULL, 0);
}


static njs_int_t
njs_generate_block_statement_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generate_patch_block_exit(vm, generator);

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_children(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    njs_generator_next(generator, njs_generate, node->left);

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node->right,
                              njs_generate, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node->left,
                               njs_generate_node_index_release_pop, NULL, 0);
}


static njs_int_t
njs_generate_stop_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_queue_link_t   *link;

    link = njs_queue_first(&generator->stack);

    ret = njs_generate_children(vm, generator, node);
    if (njs_fast_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator, link, node,
                               njs_generate_stop_statement_end, NULL, 0);
}


static njs_int_t
njs_generate_stop_statement_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t        index;
    njs_vmcode_stop_t  *stop;

    njs_generate_code(generator, njs_vmcode_stop_t, stop,
                      NJS_VMCODE_STOP, node);

    index = njs_scope_undefined_index(vm, 0);
    node = node->right;

    if (node != NULL) {
        if ((node->index != NJS_INDEX_NONE
             && node->token_type != NJS_TOKEN_FUNCTION_DECLARATION
             && node->token_type != NJS_TOKEN_ASYNC_FUNCTION_DECLARATION)
            || node->token_type == NJS_TOKEN_THIS)
        {
            index = node->index;
        }
    }

    stop->retval = index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_comma_expression(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t         ret;
    njs_queue_link_t  *link;

    link = njs_queue_first(&generator->stack);

    ret = njs_generate_children(vm, generator, node);
    if (njs_fast_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator, link, node,
                               njs_generate_comma_expression_end, NULL, 0);
}


static njs_int_t
njs_generate_comma_expression_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    node->index = node->right->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_global_property_set(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node_dst, njs_parser_node_t *node_src)
{
    njs_value_t            property;
    njs_variable_t         *var;
    njs_vmcode_prop_set_t  *prop_set;

    var = njs_variable_reference(vm, node_dst);
    if (var == NULL) {
        njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
                          NJS_VMCODE_PROPERTY_ATOM_SET, node_src);

        prop_set->value = node_dst->index;
        prop_set->object = njs_scope_global_this_index();

        njs_atom_to_value(vm, &property,node_dst->u.reference.atom_id);

        prop_set->property = njs_scope_global_index(vm, &property,
                                                generator->runtime);
        if (njs_slow_path(prop_set->property == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

    }

    return NJS_OK;
}


static njs_int_t
njs_generate_property_lvalue(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_generator_state_func_t after, void *ctx,
    size_t ctx_size)
{
    njs_int_t          ret;
    njs_parser_node_t  *lvalue;

    lvalue = node->left;

    /* Object. */

    njs_generator_next(generator, njs_generate, lvalue->left);

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              after, ctx, ctx_size);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* Property. */

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               lvalue->right, njs_generate, NULL, 0);
}


static njs_int_t
njs_generate_preserve_property_lvalue(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *lvalue,
    njs_parser_node_t *expr)
{
    njs_index_t        index, src;
    njs_parser_node_t  *object, *property;
    njs_vmcode_move_t  *move;

    if (!njs_slow_path(njs_parser_has_side_effect(expr))) {
        return NJS_OK;
    }

    object = lvalue->left;
    property = lvalue->right;

    /*
     * Preserve object and property values stored in variables in a case
     * if the variables can be changed by side effects in expression.
     */
    if (object->token_type == NJS_TOKEN_NAME) {
        src = object->index;

        index = njs_generate_node_temp_index_get(vm, generator, object);
        if (njs_slow_path(index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        njs_generate_code_move(generator, move, index, src, object);
    }

    if (property->token_type == NJS_TOKEN_NAME) {
        src = property->index;

        index = njs_generate_node_temp_index_get(vm, generator, property);
        if (njs_slow_path(index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        njs_generate_code_move(generator, move, index, src, property);
    }

    return NJS_OK;
}


static njs_int_t
njs_generate_read_property_assignment(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node,
    njs_parser_node_t *expr, njs_index_t *prop_index)
{
    njs_int_t          ret;
    njs_index_t        index;
    njs_parser_node_t  *lvalue, *object, *property;

    lvalue = node->left;
    object = lvalue->left;
    property = lvalue->right;

    ret = njs_generate_preserve_property_lvalue(vm, generator, lvalue, expr);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    *prop_index = njs_generate_property_index(vm, generator, node, object,
                                              property);
    if (njs_slow_path(*prop_index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    index = njs_generate_node_temp_index_get(vm, generator, node);
    if (njs_slow_path(index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    ret = njs_generate_property_get(vm, generator, property, index,
                                    object->index, *prop_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    node->index = index;

    return NJS_OK;
}


static njs_index_t
njs_generate_property_index(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_parser_node_t *object,
    njs_parser_node_t *property)
{
    njs_index_t         prop_index;
    njs_vmcode_3addr_t  *to_property_key;

    prop_index = property->index;

    if (!njs_parser_is_primitive(property)) {
        prop_index = njs_generate_node_temp_index_get(vm, generator, node);
        if (njs_slow_path(prop_index == NJS_INDEX_ERROR)) {
            return NJS_INDEX_ERROR;
        }

        njs_generate_code(generator, njs_vmcode_3addr_t, to_property_key,
                          NJS_VMCODE_TO_PROPERTY_KEY_CHK, property);

        to_property_key->src2 = object->index;
        to_property_key->src1 = property->index;
        to_property_key->dst = prop_index;
    }

    return prop_index;
}


static njs_vmcode_t
njs_generate_property_get_opcode(njs_parser_node_t *node)
{
    if (node->token_type == NJS_TOKEN_STRING
        || (node->token_type == NJS_TOKEN_NUMBER
            && node->u.value.atom_id != NJS_ATOM_STRING_unknown))
    {
        return NJS_VMCODE_PROPERTY_ATOM_GET;
    }

    return NJS_VMCODE_PROPERTY_GET;
}


static njs_vmcode_t
njs_generate_property_set_opcode(njs_parser_node_t *node)
{
    if (node->token_type == NJS_TOKEN_STRING
        || (node->token_type == NJS_TOKEN_NUMBER
            && node->u.value.atom_id != NJS_ATOM_STRING_unknown))
    {
        return NJS_VMCODE_PROPERTY_ATOM_SET;
    }

    return NJS_VMCODE_PROPERTY_SET;
}


static njs_int_t
njs_generate_property_get(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_index_t value, njs_index_t object,
    njs_index_t property)
{
    njs_vmcode_prop_get_t  *prop_get;

    njs_generate_code(generator, njs_vmcode_prop_get_t, prop_get,
                      njs_generate_property_get_opcode(node), node);

    prop_get->value = value;
    prop_get->object = object;
    prop_get->property = property;

    return NJS_OK;
}


static njs_int_t
njs_generate_property_set(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_index_t value, njs_index_t object,
    njs_index_t property)
{
    njs_vmcode_prop_set_t  *prop_set;

    njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
                      njs_generate_property_set_opcode(node), node);

    prop_set->value = value;
    prop_set->object = object;
    prop_set->property = property;

    return NJS_OK;
}


static njs_int_t
njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_variable_t         *var;
    njs_parser_node_t      *lvalue, *expr;
    njs_vmcode_variable_t  *var_code;

    lvalue = node->left;
    expr = node->right;
    expr->dest = NULL;

    if (lvalue->token_type == NJS_TOKEN_NAME) {

        ret = njs_generate_variable(vm, generator, lvalue, NJS_REFERENCE,
                                    &var);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
                              NJS_VMCODE_ASSIGNMENT_ERROR, node);
            var_code->dst = var->index;

            return njs_generator_stack_pop(vm, generator, NULL);
        }

        expr->dest = lvalue;

        njs_generator_next(generator, njs_generate, expr);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_assignment_name, NULL, 0);
    }

    /*
     * lvalue->token == NJS_TOKEN_PROPERTY(_REF|_INIT)
     * or NJS_TOKEN_PROTO_INIT.
     */

    return njs_generate_property_lvalue(vm, generator, node,
                                        njs_generate_assignment_prop,
                                        NULL, 0);
}


static njs_int_t
njs_generate_assignment_name(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *lvalue, *expr;
    njs_vmcode_move_t  *move;

    lvalue = node->left;
    expr = node->right;

    /*
     * lvalue and expression indexes are equal if the expression is an
     * empty object or expression result is stored directly in variable.
     */
    if (lvalue->index != expr->index) {
        njs_generate_code_move(generator, move, lvalue->index, expr->index,
                               expr);
    }

    node->index = expr->index;
    node->temporary = expr->temporary;

    ret = njs_generate_global_property_set(vm, generator, node->left, expr);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_assignment_prop(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *expr;

    expr = node->right;
    ret = njs_generate_preserve_property_lvalue(vm, generator, node->left,
                                                expr);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generator_next(generator, njs_generate, expr);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_assignment_end, NULL, 0);
}


static njs_int_t
njs_generate_assignment_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_index_t            prop_index;
    njs_vmcode_t           opcode;
    njs_parser_node_t      *lvalue, *expr, *object, *property;
    njs_vmcode_2addr_t     *set_function, *to_prop_key;
    njs_vmcode_prop_set_t  *prop_set;

    lvalue = node->left;
    expr = node->right;

    object = lvalue->left;
    property = lvalue->right;
    prop_index = property->index;

    switch (lvalue->token_type) {
    case NJS_TOKEN_PROPERTY_INIT:

        if ((object->token_type == NJS_TOKEN_OBJECT
             || njs_generate_is_object_literal_value(object))
            && (expr->token_type == NJS_TOKEN_FUNCTION
                || expr->token_type == NJS_TOKEN_FUNCTION_EXPRESSION
                || expr->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION))
        {
            if (property->token_type == NJS_TOKEN_STRING) {
                njs_value_assign(&expr->u.value.data.u.lambda->name,
                                 &property->u.value);

            } else {
                njs_generate_code(generator, njs_vmcode_2addr_t, to_prop_key,
                                  NJS_VMCODE_TO_PROPERTY_KEY, property);

                prop_index = njs_generate_temp_index_get(vm, generator,
                                                         property);
                if (njs_slow_path(prop_index == NJS_INDEX_ERROR)) {
                    return NJS_ERROR;
                }

                to_prop_key->src = property->index;
                to_prop_key->dst = prop_index;

                njs_generate_code(generator, njs_vmcode_2addr_t, set_function,
                                  NJS_VMCODE_SET_FUNCTION_NAME, expr);

                set_function->dst = expr->index;
                set_function->src = prop_index;
            }
        }

        njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
                          NJS_VMCODE_PROPERTY_INIT, expr);
        break;

    case NJS_TOKEN_PROTO_INIT:
        njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
                          NJS_VMCODE_PROTO_INIT, expr);
        break;

    default:
        opcode = njs_generate_property_set_opcode(property);

        njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set, opcode,
                          expr);
    }

    prop_set->value = expr->index;
    prop_set->object = object->index;
    prop_set->property = prop_index;

    if (prop_index != property->index) {
        ret = njs_generate_index_release(vm, generator, prop_index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    node->index = expr->index;
    node->temporary = expr->temporary;

    ret = njs_generate_children_indexes_release(vm, generator, lvalue);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_operation_assignment(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_index_t            index;
    njs_variable_t         *var;
    njs_parser_node_t      *lvalue, *expr;
    njs_vmcode_move_t      *move;
    njs_vmcode_variable_t  *var_code;

    lvalue = node->left;

    if (lvalue->token_type == NJS_TOKEN_NAME) {

        ret = njs_generate_variable(vm, generator, lvalue, NJS_REFERENCE,
                                    &var);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
                              NJS_VMCODE_ASSIGNMENT_ERROR, node);
            var_code->dst = var->index;

            return njs_generator_stack_pop(vm, generator, NULL);
        }

        index = lvalue->index;
        expr = node->right;

        if (njs_slow_path(njs_parser_has_side_effect(expr))) {
            /* Preserve variable value if it may be changed by expression. */

            njs_generate_code(generator, njs_vmcode_move_t, move,
                              NJS_VMCODE_MOVE, expr);
            move->src = lvalue->index;

            index = njs_generate_temp_index_get(vm, generator, expr);
            if (njs_slow_path(index == NJS_INDEX_ERROR)) {
                return NJS_ERROR;
            }

            move->dst = index;
        }

        njs_generator_next(generator, njs_generate, expr);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_operation_assignment_name,
                                   &index, sizeof(njs_index_t));
    }

    if (!njs_generate_is_property_lvalue(lvalue)) {
        njs_internal_error(vm, "unexpected assignment target");
        return NJS_ERROR;
    }

    return njs_generate_property_lvalue(vm, generator, node,
                                        njs_generate_operation_assignment_prop,
                                        NULL, 0);
}


static njs_int_t
njs_generate_operation_assignment_name(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t           ret;
    njs_index_t         index;
    njs_parser_node_t   *lvalue, *expr;
    njs_vmcode_3addr_t  *code;

    lvalue = node->left;
    expr = node->right;

    index = *((njs_index_t *) generator->context);

    njs_generate_code(generator, njs_vmcode_3addr_t, code,
                      node->u.operation, expr);
    code->dst = lvalue->index;
    code->src1 = index;
    code->src2 = expr->index;

    node->index = lvalue->index;

    ret = njs_generate_global_property_set(vm, generator, node->left, expr);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (lvalue->index != index) {
        ret = njs_generate_index_release(vm, generator, index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    njs_mp_free(vm->mem_pool, generator->context);

    return njs_generate_node_index_release_pop(vm, generator, expr);
}


static njs_int_t
njs_generate_operation_assignment_prop(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t    ret;
    njs_index_t  prop_index;

    ret = njs_generate_read_property_assignment(vm, generator, node,
                                                node->right, &prop_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_operation_assignment_end,
                               &prop_index, sizeof(njs_index_t));
}


static njs_int_t
njs_generate_operation_assignment_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t           ret;
    njs_index_t         prop_index;
    njs_parser_node_t   *lvalue, *expr;
    njs_vmcode_3addr_t  *code;

    lvalue = node->left;
    expr = node->right;

    prop_index = *((njs_index_t *) generator->context);

    njs_generate_code(generator, njs_vmcode_3addr_t, code,
                      node->u.operation, expr);
    code->dst = node->index;
    code->src1 = node->index;
    code->src2 = expr->index;

    ret = njs_generate_property_set(vm, generator, lvalue->right, node->index,
                                    lvalue->left->index, prop_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_children_indexes_release(vm, generator, lvalue);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generate_node_index_release_pop(vm, generator, expr);
}


static njs_int_t
njs_generate_logical_assignment(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                       ret;
    njs_variable_t                  *var;
    njs_parser_node_t               *lvalue;
    njs_vmcode_variable_t           *var_code;
    njs_generator_log_assign_ctx_t  ctx;

    lvalue = node->left;

    if (lvalue->token_type == NJS_TOKEN_NAME) {

        ret = njs_generate_variable(vm, generator, lvalue, NJS_REFERENCE,
                                    &var);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        ret = njs_generate_test_jump(vm, generator, node, node->u.operation,
                                     lvalue->index, lvalue->index,
                                     &ctx.jump_offset);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
                              NJS_VMCODE_ASSIGNMENT_ERROR, node);
            var_code->dst = var->index;

            njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t,
                                     ctx.jump_offset);

            node->index = lvalue->index;

            return njs_generator_stack_pop(vm, generator, NULL);
        }

        ctx.prop_index = NJS_INDEX_NONE;

        njs_generator_next(generator, njs_generate, node->right);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_logical_assignment_end,
                                   &ctx,
                                   sizeof(njs_generator_log_assign_ctx_t));
    }

    if (!njs_generate_is_property_lvalue(lvalue)) {
        njs_internal_error(vm, "unexpected logical assignment target");
        return NJS_ERROR;
    }

    /* Object. */

    njs_generator_next(generator, njs_generate, lvalue->left);

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_logical_assignment_prop, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* Property. */

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               lvalue->right, njs_generate, NULL, 0);
}


static njs_int_t
njs_generate_logical_assignment_prop(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                       ret;
    njs_generator_log_assign_ctx_t  ctx;

    ret = njs_generate_read_property_assignment(vm, generator, node,
                                                node->right, &ctx.prop_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generate_test_jump(vm, generator, node, node->u.operation,
                                 node->index, node->index,
                                 &ctx.jump_offset);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_logical_assignment_end,
                               &ctx,
                               sizeof(njs_generator_log_assign_ctx_t));
}


static njs_int_t
njs_generate_logical_assignment_end(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node)
{
    njs_int_t                       ret;
    njs_index_t                     index;
    njs_parser_node_t               *lvalue, *expr;
    njs_vmcode_move_t               *move;
    njs_generator_log_assign_ctx_t  *ctx;

    lvalue = node->left;
    expr = node->right;

    ctx = generator->context;

    index = (lvalue->token_type == NJS_TOKEN_NAME) ? lvalue->index
                                                   : node->index;

    if (index != expr->index) {
        njs_generate_code_move(generator, move, index,
                               expr->index, node);
    }

    if (ctx->prop_index == NJS_INDEX_NONE) {
        ret = njs_generate_global_property_set(vm, generator, lvalue, expr);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t,
                                 ctx->jump_offset);

        node->index = lvalue->index;

        ret = njs_generate_node_index_release(vm, generator, expr);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        return njs_generator_stack_pop(vm, generator, generator->context);
    }

    ret = njs_generate_property_set(vm, generator, lvalue->right, node->index,
                                    lvalue->left->index, ctx->prop_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t,
                             ctx->jump_offset);

    ret = njs_generate_children_indexes_release(vm, generator, lvalue);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generate_node_index_release_pop(vm, generator, expr);
}


static njs_int_t
njs_generate_object(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_object_t  *object;

    node->index = njs_generate_object_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_object_t, object,
                      NJS_VMCODE_OBJECT, node);
    object->retval = node->index;

    /* Initialize object. */

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               NULL, njs_generator_pop, NULL, 0);
}


static njs_int_t
njs_generate_property_accessor(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    njs_generator_next(generator, njs_generate, node->left->left);

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_property_accessor_end, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack),
                              node->right, njs_generate, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               node->left->right, njs_generate, NULL, 0);
}


static njs_int_t
njs_generate_property_accessor_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_node_t           *lvalue, *function;
    njs_vmcode_prop_accessor_t  *accessor;

    lvalue = node->left;
    function = node->right;

    njs_generate_code(generator, njs_vmcode_prop_accessor_t, accessor,
                      NJS_VMCODE_PROPERTY_ACCESSOR, function);

    accessor->value = function->index;
    accessor->object = lvalue->left->index;
    accessor->property = lvalue->right->index;
    accessor->type = (node->token_type == NJS_TOKEN_PROPERTY_GETTER)
                     ? NJS_OBJECT_PROP_GETTER : NJS_OBJECT_PROP_SETTER;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_array(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_array_t  *array;

    node->index = njs_generate_object_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_array_t, array,
                      NJS_VMCODE_ARRAY, node);
    array->ctor = node->ctor;
    array->retval = node->index;
    array->length = node->u.length;

    /* Initialize array. */

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               NULL, njs_generator_pop, NULL, 0);
}


static njs_int_t
njs_generate_function_expression(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_str_t              name;
    njs_variable_t         *var;
    njs_function_lambda_t  *lambda;
    njs_vmcode_function_t  *function;

    var = njs_variable_reference(vm, node->left);
    if (njs_slow_path(var == NULL)) {
        ret = njs_generate_reference_error(vm, generator, node->left);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        return njs_generator_stack_pop(vm, generator, NULL);
    }

    lambda = node->u.value.data.u.lambda;

    njs_atom_to_value(vm, &lambda->name, var->atom_id);
    njs_string_get(vm, &lambda->name, &name);

    ret = njs_generate_function_scope(vm, generator, lambda, node, &name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_code(generator, njs_vmcode_function_t, function,
                      NJS_VMCODE_FUNCTION, node);
    function->lambda = lambda;
    function->async = (node->token_type == NJS_TOKEN_ASYNC_FUNCTION_EXPRESSION);

    node->index = njs_generate_object_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    function->retval = node->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_function(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_function_lambda_t  *lambda;
    njs_vmcode_function_t  *function;

    lambda = node->u.value.data.u.lambda;

    ret = njs_generate_function_scope(vm, generator, lambda, node,
                                      &njs_entry_empty);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generate_code(generator, njs_vmcode_function_t, function,
                      NJS_VMCODE_FUNCTION, node);
    function->lambda = lambda;
    function->async = (node->token_type == NJS_TOKEN_ASYNC_FUNCTION);

    node->index = njs_generate_object_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    function->retval = node->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_regexp(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_regexp_t  *regexp;

    node->index = njs_generate_object_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_regexp_t, regexp,
                      NJS_VMCODE_REGEXP, node);
    regexp->retval = node->index;
    regexp->pattern = node->u.value.data.u.data;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_template_literal(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_template_literal_end, NULL, 0);
}


static njs_int_t
njs_generate_template_literal_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_template_literal_t  *code;

    njs_generate_code(generator, njs_vmcode_template_literal_t, code,
                      NJS_VMCODE_TEMPLATE_LITERAL, node);
    code->retval = node->left->index;

    node->index = node->left->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_test_jump(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_vmcode_t opcode, njs_index_t value,
    njs_index_t retval, njs_jump_off_t *jump_offset)
{
    njs_vmcode_test_jump_t  *test_jump;

    njs_generate_code(generator, njs_vmcode_test_jump_t, test_jump, opcode,
                      node);

    *jump_offset = njs_code_offset(generator, test_jump);
    test_jump->value = value;
    test_jump->retval = retval;

    return NJS_OK;
}


static njs_int_t
njs_generate_test_jump_expression(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_test_jump_expression_after,
                               NULL, 0);
}


static njs_int_t
njs_generate_test_jump_expression_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node)
{
    njs_int_t       ret;
    njs_jump_off_t  jump_offset;

    node->index = njs_generate_node_temp_index_get(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return node->index;
    }

    ret = njs_generate_test_jump(vm, generator, node, node->u.operation,
                                 node->left->index, node->index,
                                 &jump_offset);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_test_jump_expression_end,
                               &jump_offset, sizeof(njs_jump_off_t));
}


static njs_int_t
njs_generate_test_jump_expression_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_vmcode_move_t  *move;

    /*
     * The right expression usually uses node->index as destination,
     * however, if the expression is a literal, variable or assignment,
     * then a MOVE operation is required.
     */

    if (node->index != node->right->index) {
        njs_generate_code_move(generator, move, node->index,
                               node->right->index, node);
    }

    njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t,
                             *((njs_jump_off_t *) generator->context));

    ret = njs_generate_children_indexes_release(vm, generator, node);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, generator->context);
}


static njs_parser_node_t *
njs_generate_optional_method_call(njs_vm_t *vm, njs_parser_node_t *node)
{
    njs_parser_node_t  *preserve;

    if (node == NULL || node->token_type != NJS_TOKEN_METHOD_CALL) {
        return NULL;
    }

    preserve = njs_generate_optional_method_call_preserve(node);
    if (preserve == NULL) {
        return NULL;
    }

    if (!njs_generate_is_property_call_source(node->left)) {
        njs_internal_error(vm, "unexpected optional method call source");
        return NULL;
    }

    if (preserve->left == NULL || preserve->right == NULL) {
        njs_internal_error(vm, "unexpected optional method call state");
        return NULL;
    }

    if (!njs_generate_is_property_lvalue(preserve)) {
        njs_internal_error(vm, "unexpected optional method call preserve");
        return NULL;
    }

    return node;
}


static njs_int_t
njs_generate_optional_chain(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_jump_off_t     jump_offset;
    njs_parser_node_t  *call, *preserve;

    jump_offset = 0;

    call = njs_generate_optional_method_call(vm, node->right);
    if (call != NULL) {
        preserve = njs_generate_optional_method_call_base(call);

        if (njs_generate_is_property_lvalue(preserve)) {
            preserve->hoist = 1;
        }
    }

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               node, njs_generate_optional_chain_after,
                               &jump_offset, sizeof(njs_jump_off_t));
}


static njs_int_t
njs_generate_optional_chain_after(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_jump_off_t          *jump_offset;
    njs_parser_node_t       *call, *prop;
    njs_vmcode_test_jump_t  *test_jump;

    jump_offset = generator->context;

    njs_generate_code(generator, njs_vmcode_test_jump_t, test_jump,
                      NJS_VMCODE_OPTIONAL_CHAIN, node);
    *jump_offset = njs_code_offset(generator, test_jump);
    test_jump->value = node->left->index;

    node->index = njs_generate_node_temp_index_get(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return node->index;
    }

    test_jump->retval = node->index;

    call = njs_generate_optional_method_call(vm, node->right);
    if (call != NULL) {
        prop = njs_generate_optional_method_call_property(call);
        prop->left->index = njs_generate_optional_method_call_base(call)
                                ->index;
        prop->right->index = njs_generate_optional_method_call_key(call)
                                 ->index;

    } else if (njs_generate_optional_chain_preserve(node) != NULL) {
        njs_generate_optional_chain_preserve(node)->index = node->left->index;
    }

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack),
                               node, njs_generate_optional_chain_end,
                               jump_offset, sizeof(njs_jump_off_t));
}


static njs_int_t
njs_generate_optional_chain_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_jump_off_t     *jump_offset;
    njs_vmcode_move_t  *move;
    njs_parser_node_t  *call, *preserve;

    jump_offset = generator->context;

    if (node->index != node->right->index) {
        njs_generate_code_move(generator, move, node->index,
                               node->right->index, node);
    }

    njs_code_set_jump_offset(generator, njs_vmcode_test_jump_t,
                             *jump_offset);

    call = njs_generate_optional_method_call(vm, node->right);
    if (call != NULL) {
        preserve = njs_generate_optional_method_call_base(call);
        if (!njs_generate_is_property_lvalue(preserve)) {
            preserve = NULL;
        }
    } else {
        preserve = NULL;
    }

    if (preserve != NULL) {
        ret = njs_generate_index_release(vm, generator,
                                         preserve->index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    ret = njs_generate_children_indexes_release(vm, generator, node);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, generator->context);
}


static njs_int_t
njs_generate_3addr_operation(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *left, *right;

    left = node->left;
    right = node->right;

    njs_generator_next(generator, njs_generate, left);

    if (left->token_type == NJS_TOKEN_NAME) {
        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_3addr_operation_name,
                                   NULL, 0);
    }

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_3addr_operation_end, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), right,
                               njs_generate, NULL, 0);
}


static njs_int_t
njs_generate_3addr_operation_name(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t        index;
    njs_parser_node_t  *left;
    njs_vmcode_move_t  *move;

    left = node->left;

    if (njs_slow_path(njs_parser_has_side_effect(node->right))) {
        njs_generate_code(generator, njs_vmcode_move_t, move,
                          NJS_VMCODE_MOVE, node);
        move->src = left->index;

        index = njs_generate_node_temp_index_get(vm, generator, left);
        if (njs_slow_path(index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        move->dst = index;
    }

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_3addr_operation_end,
                               NULL, 0);
}


static njs_int_t
njs_generate_3addr_operation_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_bool_t          swap;
    njs_vmcode_t        opcode;
    njs_parser_node_t   *left, *right;
    njs_vmcode_3addr_t  *code;

    left = node->left;
    right = node->right;

    if (node->u.operation == NJS_VMCODE_PROPERTY_GET
        && (right->token_type == NJS_TOKEN_STRING
            || (right->token_type == NJS_TOKEN_NUMBER
                && right->u.value.atom_id != NJS_ATOM_STRING_unknown)))
    {
        opcode = NJS_VMCODE_PROPERTY_ATOM_GET;

    } else {
        opcode = node->u.operation;
    }

    njs_generate_code(generator, njs_vmcode_3addr_t, code,
                      opcode, node);

    swap = (node->token_type == NJS_TOKEN_IN);

    if (!swap) {
        code->src1 = left->index;
        code->src2 = right->index;

    } else {
        code->src1 = right->index;
        code->src2 = left->index;
    }

    /*
     * The temporary index of MOVE destination
     * will be released here as index of node->left.
     */
    node->index = njs_generate_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return node->index;
    }

    code->dst = node->index;

    njs_debug_generator_code(code);

    return njs_generator_stack_pop(vm, generator, generator->context);
}


static njs_int_t
njs_generate_2addr_operation(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_2addr_operation_end, NULL, 0);
}


static njs_int_t
njs_generate_2addr_operation_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_2addr_t  *code;

    njs_generate_code(generator, njs_vmcode_2addr_t, code,
                      node->u.operation, node);
    code->src = node->left->index;

    node->index = njs_generate_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return node->index;
    }

    code->dst = node->index;

    njs_debug_generator_code(code);

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_typeof_operation(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *expr;

    expr = node->left;

    if (expr->token_type != NJS_TOKEN_NAME) {
        njs_generator_next(generator, njs_generate, node->left);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_typeof_operation_end, NULL, 0);
    }

    ret = njs_generate_variable(vm, generator, expr, NJS_TYPEOF, NULL);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    return njs_generate_typeof_operation_end(vm, generator, node);
}


static njs_int_t
njs_generate_typeof_operation_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_2addr_t  *code;

    njs_generate_code(generator, njs_vmcode_2addr_t, code,
                      node->u.operation, node->left);
    code->src = node->left->index;

    node->index = njs_generate_dest_index(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return node->index;
    }

    code->dst = node->index;

    njs_debug_generator_code(code);

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_inc_dec_operation(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_bool_t post)
{
    njs_int_t              ret;
    njs_index_t            index;
    njs_variable_t         *var;
    njs_parser_node_t      *lvalue;
    njs_vmcode_3addr_t     *code;
    njs_vmcode_variable_t  *var_code;

    lvalue = node->left;

    if (lvalue->token_type == NJS_TOKEN_NAME) {

        ret = njs_generate_variable(vm, generator, lvalue, NJS_REFERENCE,
                                    &var);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        if (var != NULL && var->type == NJS_VARIABLE_CONST) {
            njs_generate_code(generator, njs_vmcode_variable_t, var_code,
                              NJS_VMCODE_ASSIGNMENT_ERROR, node);
            var_code->dst = var->index;

            return njs_generator_stack_pop(vm, generator, NULL);
        }

        index = njs_generate_dest_index(vm, generator, node);
        if (njs_slow_path(index == NJS_INDEX_ERROR)) {
            return index;
        }

        node->index = index;

        njs_generate_code(generator, njs_vmcode_3addr_t, code,
                          node->u.operation, node);
        code->dst = index;
        code->src1 = lvalue->index;
        code->src2 = lvalue->index;

        ret = njs_generate_global_property_set(vm, generator, lvalue, lvalue);
        if (njs_slow_path(ret) != NJS_OK) {
            return ret;
        }

        return njs_generator_stack_pop(vm, generator, NULL);
    }

    if (!njs_generate_is_property_lvalue(lvalue)) {
        njs_internal_error(vm, "unexpected increment/decrement target");
        return NJS_ERROR;
    }

    return njs_generate_property_lvalue(vm, generator, node,
                                        njs_generate_inc_dec_operation_prop,
                                        &post, sizeof(njs_bool_t));
}


static njs_int_t
njs_generate_inc_dec_operation_prop(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_bool_t             post;
    njs_index_t            index, dest_index, prop_index;
    njs_vmcode_t           opcode;
    njs_parser_node_t      *lvalue, *prop;
    njs_vmcode_3addr_t     *code, *to_property_key;
    njs_vmcode_prop_get_t  *prop_get;
    njs_vmcode_prop_set_t  *prop_set;

    lvalue = node->left;

    if (node->dest != NULL) {
        dest_index = node->dest->index;

        if (dest_index != NJS_INDEX_NONE
            && dest_index != lvalue->left->index
            && dest_index != lvalue->right->index)
        {
            node->index = dest_index;
            goto found;
        }
    }

    dest_index = njs_generate_node_temp_index_get(vm, generator, node);

found:

    prop_index = lvalue->right->index;

    if (!njs_parser_is_primitive(lvalue->right)) {
        prop_index = njs_generate_temp_index_get(vm, generator, node);
        if (njs_slow_path(prop_index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        njs_generate_code(generator, njs_vmcode_3addr_t, to_property_key,
                          NJS_VMCODE_TO_PROPERTY_KEY_CHK, node);

        to_property_key->src2 = lvalue->left->index;
        to_property_key->src1 = lvalue->right->index;
        to_property_key->dst = prop_index;
    }

    post = *((njs_bool_t *) generator->context);

    index = post ? njs_generate_temp_index_get(vm, generator, node)
                 : dest_index;

    if (njs_slow_path(index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    prop = lvalue->right;

    if (prop->token_type == NJS_TOKEN_STRING
        || (prop->token_type == NJS_TOKEN_NUMBER
            && prop->u.value.atom_id != NJS_ATOM_STRING_unknown))
    {
        opcode = NJS_VMCODE_PROPERTY_ATOM_GET;

    } else {
        opcode = NJS_VMCODE_PROPERTY_GET;
    }

    njs_generate_code(generator, njs_vmcode_prop_get_t, prop_get,
                      opcode, node);

    prop_get->value = index;
    prop_get->object = lvalue->left->index;
    prop_get->property = prop_index;

    njs_generate_code(generator, njs_vmcode_3addr_t, code,
                      node->u.operation, node);
    code->dst = dest_index;
    code->src1 = index;
    code->src2 = index;

    if (opcode == NJS_VMCODE_PROPERTY_ATOM_GET) {
        opcode = NJS_VMCODE_PROPERTY_ATOM_SET;

    } else {
        opcode = NJS_VMCODE_PROPERTY_SET;
    }

    njs_generate_code(generator, njs_vmcode_prop_set_t, prop_set,
                      opcode, node);

    prop_set->value = index;
    prop_set->object = lvalue->left->index;
    prop_set->property = prop_index;

    if (post) {
        ret = njs_generate_index_release(vm, generator, index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    njs_mp_free(vm->mem_pool, generator->context);

    ret = njs_generate_children_indexes_release(vm, generator, lvalue);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_function_declaration(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t              ret;
    njs_str_t              name;
    njs_bool_t             async;
    njs_variable_t         *var;
    njs_function_t         *function;
    njs_function_lambda_t  *lambda;

    var = njs_variable_reference(vm, node);
    if (njs_slow_path(var == NULL)) {
        ret = njs_generate_reference_error(vm, generator, node);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        return njs_generator_stack_pop(vm, generator, NULL);
    }

    lambda = njs_variable_lambda(var);

    njs_atom_to_value(vm, &lambda->name, node->u.reference.atom_id);
    njs_string_get(vm, &lambda->name, &name);

    ret = njs_generate_function_scope(vm, generator, lambda, node, &name);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    async = (node->token_type == NJS_TOKEN_ASYNC_FUNCTION_DECLARATION);
    function = njs_function_alloc(vm, lambda, async);
    if (njs_slow_path(function == NULL)) {
        return NJS_ERROR;
    }

    function->global = njs_function_scope(var->scope)->type == NJS_SCOPE_GLOBAL;
    function->object.shared = 1;
    function->args_count = lambda->nargs - lambda->rest_parameters;

    njs_set_function(&var->value, function);

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev,
    njs_function_lambda_t *lambda, njs_parser_node_t *node,
    const njs_str_t *name)
{
    njs_int_t        ret;
    njs_uint_t       depth;
    njs_vm_code_t    *code;
    njs_generator_t  generator;

    depth = prev->depth;

    if (++depth >= NJS_FUNCTION_MAX_DEPTH) {
        njs_range_error(vm, "Maximum function nesting depth exceeded");
        return NJS_ERROR;
    }

    ret = njs_generator_init(&generator, &prev->file, depth, prev->runtime);
    if (njs_slow_path(ret != NJS_OK)) {
        njs_internal_error(vm, "njs_generator_init() failed");
        return NJS_ERROR;
    }

    node = node->right;

    code = njs_generate_scope(vm, &generator, node->scope, name);
    if (njs_slow_path(code == NULL)) {
        if (!njs_is_error(&vm->exception)) {
            njs_internal_error(vm, "njs_generate_scope() failed");
        }

        return NJS_ERROR;
    }

    lambda->start = generator.code_start;
    lambda->closures = generator.closures->start;
    lambda->nclosures = generator.closures->items;
    lambda->nlocal = node->scope->items;

    return NJS_OK;
}


njs_vm_code_t *
njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_scope_t *scope, const njs_str_t *name)
{
    u_char                 *p, *code_start;
    size_t                 code_size, prelude;
    int64_t                nargs;
    njs_int_t              ret;
    njs_arr_t              *arr;
    njs_uint_t             index, n;
    njs_vm_code_t          *code;
    njs_declaration_t      *declr;
    njs_vmcode_function_t  *fun;

    generator->code_size = 128;

    p = njs_mp_alloc(vm->mem_pool, generator->code_size);
    if (njs_slow_path(p == NULL)) {
        njs_memory_error(vm);
        return NULL;
    }

    generator->code_start = p;
    generator->code_end = p;

    nargs = njs_generate_lambda_variables(vm, generator, scope->top);
    if (njs_slow_path(nargs < NJS_OK)) {
        return NULL;
    }

    if (vm->codes == NULL) {
        vm->codes = njs_arr_create(vm->mem_pool, 4, sizeof(njs_vm_code_t));
        if (njs_slow_path(vm->codes == NULL)) {
            return NULL;
        }
    }

    index = vm->codes->items;
    code = njs_arr_add(vm->codes);
    if (njs_slow_path(code == NULL)) {
        njs_memory_error(vm);
        return NULL;
    }

    code->lines = NULL;

    if (vm->options.backtrace) {
        code->lines = njs_arr_create(vm->mem_pool, 4,
                                     sizeof(njs_vm_line_num_t));
        if (njs_slow_path(code->lines == NULL)) {
            njs_memory_error(vm);
            return NULL;
        }

        generator->lines = code->lines;
    }

    generator->closures = njs_arr_create(vm->mem_pool, 4, sizeof(njs_index_t));
    if (njs_slow_path(generator->closures == NULL)) {
        return NULL;
    }

    scope->closures = generator->closures;

    njs_queue_init(&generator->stack);

    njs_generator_next(generator, njs_generate, scope->top);

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), NULL,
                              njs_generate_scope_end, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return NULL;
    }

    do {
        ret = generator->state(vm, generator, generator->node);
        if (njs_slow_path(ret != NJS_OK)) {
            return NULL;
        }

    } while (generator->state != NULL);

    arr = scope->declarations;
    scope->declarations = NULL;

    if (arr != NULL && arr->items > 0) {
        declr = arr->start;
        prelude = sizeof(njs_vmcode_function_t) * arr->items;
        code_size = generator->code_end - generator->code_start;

        for (n = 0; n < arr->items; n++) {
            fun = (njs_vmcode_function_t *) njs_generate_reserve(vm,
                                     generator, sizeof(njs_vmcode_function_t));
            if (njs_slow_path(fun == NULL)) {
                njs_memory_error(vm);
                return NULL;
            }

            generator->code_end += sizeof(njs_vmcode_function_t);
            fun->code = NJS_VMCODE_FUNCTION;

            fun->lambda = declr[n].lambda;
            fun->async = declr[n].async;
            fun->retval = declr[n].index;
        }

        code_start = njs_mp_alloc(vm->mem_pool, code_size + prelude);
        if (njs_slow_path(code_start == NULL)) {
            njs_memory_error(vm);
            return NULL;
        }

        memcpy(code_start, &generator->code_start[code_size], prelude);
        memcpy(&code_start[prelude], generator->code_start, code_size);

        njs_mp_free(vm->mem_pool, generator->code_start);

        generator->code_start = code_start;
        generator->code_end = code_start + code_size + prelude;
    }

    code = njs_arr_item(vm->codes, index);
    code->start = generator->code_start;
    code->end = generator->code_end;
    code->file = generator->file;
    code->name = *name;

    generator->code_size = generator->code_end - generator->code_start;

    return code;
}


static njs_int_t
njs_generate_scope_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    generator->state = NULL;
    return NJS_OK;
}


static int64_t
njs_generate_lambda_variables(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    int64_t                 nargs;
    njs_variable_t          *var;
    njs_rbtree_node_t       *rb_node;
    njs_variable_node_t     *var_node;
    njs_vmcode_arguments_t  *arguments;

    nargs = 0;

    rb_node = njs_rbtree_min(&node->scope->variables);

    while (njs_rbtree_is_there_successor(&node->scope->variables, rb_node)) {
        var_node = (njs_variable_node_t *) rb_node;
        var = var_node->variable;

        if (var == NULL) {
            break;
        }

        if (var->argument) {
            nargs++;
        }

        if (var->arguments_object) {
            njs_generate_code(generator, njs_vmcode_arguments_t, arguments,
                              NJS_VMCODE_ARGUMENTS, NULL);
            arguments->dst = var->index;
        }

        rb_node = njs_rbtree_node_successor(&node->scope->variables, rb_node);
    }

    return nargs;
}


static njs_int_t
njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_return_statement_end, NULL, 0);
}


static njs_int_t
njs_generate_return_statement_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t              index;
    const njs_str_t          *dest;
    njs_vmcode_return_t      *code;
    njs_generator_patch_t    *patch;
    njs_generator_block_t    *block, *immediate, *top;
    njs_vmcode_try_return_t  *try_return;

    if (node->right != NULL) {
        index = node->right->index;

    } else {
        index = njs_scope_global_index(vm, &njs_value_undefined,
                                       generator->runtime);
    }

    if (njs_slow_path(index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    immediate = njs_generate_lookup_block(generator->block, NJS_GENERATOR_TRY,
                                          &no_label);

    njs_debug_generator(vm, "LOOKUP TRY   %p", immediate);

    if (njs_fast_path(immediate == NULL)) {
        njs_generate_code(generator, njs_vmcode_return_t, code,
                          NJS_VMCODE_RETURN, node);
        code->retval = index;
        node->index = index;

        return njs_generator_stack_pop(vm, generator, NULL);
    }

    if (immediate->type == NJS_GENERATOR_TRY && immediate->exit != NULL) {
        dest = njs_generate_jump_destination(vm, immediate->next,
                                             "break/return",
                                             NJS_GENERATOR_ALL,
                                             &immediate->exit->label,
                                             &return_label);
        if (njs_slow_path(dest == NULL)) {
            return NJS_ERROR;
        }
    }

    top = immediate;
    block = immediate->next;

    while (block != NULL) {
        if (block->type & NJS_GENERATOR_TRY) {
            top = block;
        }

        block = block->next;
    }

    njs_generate_code(generator, njs_vmcode_try_return_t, try_return,
                      NJS_VMCODE_TRY_RETURN, node);
    try_return->retval = index;
    try_return->save = top->index;
    try_return->offset = offsetof(njs_vmcode_try_return_t, offset);

    patch = njs_generate_make_exit_patch(vm, immediate, &return_label,
                                         njs_code_offset(generator, try_return)
                                         + offsetof(njs_vmcode_try_return_t,
                                                    offset));
    if (njs_slow_path(patch == NULL)) {
        return NJS_ERROR;
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_function_call(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_node_t  *callee, *this_object;

    callee = node->left;
    this_object = njs_generate_function_call_this(node);

    njs_assert(callee != NULL);

    if (njs_generate_is_property_call_source(callee)) {
        njs_internal_error(vm, "unexpected function call source");
        return NJS_ERROR;
    }

    if (this_object != NULL
        && callee->token_type != NJS_TOKEN_OPTIONAL_CHAIN)
    {
        njs_internal_error(vm, "unexpected function call this");
        return NJS_ERROR;
    }

    njs_generator_next(generator, njs_generate, callee);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_function_call_arguments,
                               NULL, 0);
}


static njs_int_t
njs_generate_function_call_arguments(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    ret = njs_generate_capture_stable_value(vm, generator, node->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (node->right == NULL) {
        return njs_generate_function_call_frame(vm, generator, node);
    }

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_function_call_frame, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generate_call_argument_expressions(vm, generator, node->right);
}


static njs_int_t
njs_generate_function_call_frame(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_uint_t                   nargs;
    njs_parser_node_t            *callee, *this_object;
    njs_parser_node_t            *arg;
    njs_vmcode_1addr_t           *put_arg;
    njs_vmcode_method_frame_t    *method_frame;
    njs_vmcode_function_frame_t  *func;

    callee = node->left;
    njs_assert(callee != NULL);

    this_object = njs_generate_function_call_this(node);
    nargs = njs_generate_call_nargs(node->right);

    if (this_object != NULL) {
        njs_generate_code(generator, njs_vmcode_method_frame_t, method_frame,
                          NJS_VMCODE_METHOD_FRAME, node);
        method_frame->ctor = node->ctor;
        method_frame->function = callee->index;
        method_frame->this_object = this_object->index;
        method_frame->nargs = nargs;

    } else {
        njs_generate_code(generator, njs_vmcode_function_frame_t, func,
                          NJS_VMCODE_FUNCTION_FRAME, node);
        func->ctor = node->ctor;
        func->name = callee->index;
        func->nargs = nargs;
    }

    for (arg = node->right; arg != NULL; arg = arg->right) {
        njs_generate_code(generator, njs_vmcode_1addr_t, put_arg,
                          NJS_VMCODE_PUT_ARG, arg);
        put_arg->index = arg->left->index;
    }

    return njs_generate_function_call_end(vm, generator, node);
}


static njs_int_t
njs_generate_function_call_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *this_object;

    ret = njs_generate_call(vm, generator, node);
    if (njs_fast_path(ret != NJS_OK)) {
        return ret;
    }

    this_object = njs_generate_function_call_this(node);
    if (this_object != NULL && this_object->temporary) {
        ret = njs_generate_index_release(vm, generator, this_object->index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_method_call(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *prop;

    if (njs_generate_optional_method_call(vm, node) != NULL) {
        return njs_generate_method_call_arguments(vm, generator, node);
    }

    prop = node->left;

    if (!njs_generate_is_property_call_source(prop)) {
        njs_internal_error(vm, "unexpected method call source");
        return NJS_ERROR;
    }

    /* Object. */

    njs_generator_next(generator, njs_generate, prop->left);

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_method_call_arguments, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    /* Method name. */

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), prop->right,
                               njs_generate, NULL, 0);
}


static njs_int_t
njs_generate_method_call_arguments(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *prop, *property;

    prop = node->left;
    property = prop->right;

    if (!njs_generate_is_property_call_source(prop)) {
        njs_internal_error(vm, "unexpected method call source");
        return NJS_ERROR;
    }

    if (node->right == NULL) {
        prop->index = njs_generate_node_temp_index_get(vm, generator, prop);
        if (njs_slow_path(prop->index == NJS_INDEX_ERROR)) {
            return NJS_ERROR;
        }

        ret = njs_generate_property_get(vm, generator, property, prop->index,
                                        prop->left->index, property->index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        return njs_generate_method_call_frame(vm, generator, node);
    }

    ret = njs_generate_method_call_prepare(vm, generator, node);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ret = njs_generator_after(vm, generator,
                              njs_queue_first(&generator->stack), node,
                              njs_generate_method_call_frame, NULL, 0);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generate_call_argument_expressions(vm, generator, node->right);
}


static njs_int_t
njs_generate_method_call_prepare(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t          ret;
    njs_parser_node_t  *prop, *property;

    prop = node->left;
    property = prop->right;

    ret = njs_generate_capture_stable_value(vm, generator, prop->left);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    prop->index = njs_generate_node_temp_index_get(vm, generator, prop);
    if (njs_slow_path(prop->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    return njs_generate_property_get(vm, generator, property, prop->index,
                                     prop->left->index, property->index);
}


static njs_int_t
njs_generate_method_call_frame(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_uint_t                 nargs;
    njs_parser_node_t          *arg;
    njs_parser_node_t          *prop;
    njs_vmcode_1addr_t         *put_arg;
    njs_vmcode_method_frame_t  *method_frame;

    prop = node->left;
    nargs = njs_generate_call_nargs(node->right);

    if (!njs_generate_is_property_call_source(prop)) {
        njs_internal_error(vm, "unexpected method call source");
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_method_frame_t, method_frame,
                      NJS_VMCODE_METHOD_FRAME, node);
    method_frame->ctor = node->ctor;
    method_frame->function = prop->index;
    method_frame->this_object = prop->left->index;
    method_frame->nargs = nargs;

    for (arg = node->right; arg != NULL; arg = arg->right) {
        njs_generate_code(generator, njs_vmcode_1addr_t, put_arg,
                          NJS_VMCODE_PUT_ARG, arg);
        put_arg->index = arg->left->index;
    }

    return njs_generate_method_call_end(vm, generator, node);
}


static njs_int_t
njs_generate_method_call_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    ret = njs_generate_call(vm, generator, node);
    if (njs_fast_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_call(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t                 retval;
    njs_vmcode_function_call_t  *call;

    retval = njs_generate_dest_index(vm, generator, node);
    if (njs_slow_path(retval == NJS_INDEX_ERROR)) {
        return retval;
    }

    node->index = retval;

    njs_generate_code(generator, njs_vmcode_function_call_t, call,
                      NJS_VMCODE_FUNCTION_CALL, node);
    call->retval = retval;

    return NJS_OK;
}


static njs_int_t
njs_generate_call_argument_expressions(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    if (node == NULL) {
        return njs_generator_stack_pop(vm, generator, NULL);
    }

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_call_argument_expressions_after,
                               NULL, 0);
}


static njs_int_t
njs_generate_call_argument_expressions_after(njs_vm_t *vm,
    njs_generator_t *generator, njs_parser_node_t *node)
{
    if (node->right == NULL) {
        return njs_generator_stack_pop(vm, generator, NULL);
    }

    njs_generator_next(generator, njs_generate, node->right->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node->right,
                               njs_generate_call_argument_expressions_after,
                               NULL, 0);
}


static njs_uint_t
njs_generate_call_nargs(njs_parser_node_t *node)
{
    njs_uint_t  nargs;

    nargs = 0;

    while (node != NULL) {
        nargs++;
        node = node->right;
    }

    return nargs;
}


static njs_int_t
njs_generate_capture_stable_value(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t         src;
    njs_vmcode_move_t  *move;

    if (node->temporary) {
        return NJS_OK;
    }

    src = node->index;

    node->index = njs_generate_node_temp_index_get(vm, generator, node);
    if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generate_code_move(generator, move, node->index, src, node);

    return NJS_OK;
}


#define njs_generate_code_catch(generator, _code, _exception, node)           \
    do {                                                                      \
            njs_generate_code(generator, njs_vmcode_catch_t, _code,           \
                              NJS_VMCODE_CATCH, node);                        \
            _code->offset = sizeof(njs_vmcode_catch_t);                       \
            _code->exception = _exception;                                    \
    } while (0)


#define njs_generate_code_finally(generator, _code, _retval, _exit, node)     \
    do {                                                                      \
            njs_generate_code(generator, njs_vmcode_finally_t, _code,         \
                              NJS_VMCODE_FINALLY, node);                      \
            _code->retval = _retval;                                          \
            _code->exit_value = _exit;                                        \
            _code->continue_offset = offsetof(njs_vmcode_finally_t,           \
                                              continue_offset);               \
            _code->break_offset = offsetof(njs_vmcode_finally_t,              \
                                           break_offset);                     \
    } while (0)


static njs_int_t
njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                ret;
    njs_index_t              exception_index, exit_index;
    njs_vmcode_try_start_t   *try_start;
    njs_generator_try_ctx_t  ctx;

    njs_memzero(&ctx, sizeof(njs_generator_try_ctx_t));

    njs_generate_code(generator, njs_vmcode_try_start_t, try_start,
                      NJS_VMCODE_TRY_START, node);
    ctx.try_offset = njs_code_offset(generator, try_start);

    exception_index = njs_generate_temp_index_get(vm, generator, node);
    if (njs_slow_path(exception_index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    try_start->exception_value = exception_index;

    /*
     * exit_value is used in njs_vmcode_finally to make a decision
     * which way to go after "break", "continue" and "return" instruction
     * inside "try" or "catch" blocks.
     */

    exit_index = njs_generate_temp_index_get(vm, generator, node);
    if (njs_slow_path(exit_index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    try_start->exit_value = exit_index;

    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_TRY, &no_label);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    ctx.try_block = generator->block;
    ctx.try_block->index = exit_index;
    ctx.exception_index = exception_index;

    ctx.catch_cont_label = undef_label;
    ctx.catch_exit_label = undef_label;
    ctx.try_cont_label = undef_label;
    ctx.try_exit_label = undef_label;

    njs_generator_next(generator, njs_generate, node->left);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_try_left,
                               &ctx, sizeof(njs_generator_try_ctx_t));
}


static njs_int_t
njs_generate_try_left(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                    ret;
    njs_index_t                  exit_index, catch_index;
    njs_jump_off_t               try_end_offset;
    njs_variable_t               *var;
    njs_vmcode_catch_t           *catch;
    njs_vmcode_try_end_t         *try_end;
    njs_generator_block_t        *try_block;
    njs_generator_try_ctx_t      *ctx;
    njs_vmcode_try_trampoline_t  *try_break, *try_continue;

    ctx = generator->context;

    try_block = ctx->try_block;
    exit_index = try_block->index;

    njs_generate_code(generator, njs_vmcode_try_end_t, try_end,
                      NJS_VMCODE_TRY_END, NULL);
    try_end_offset = njs_code_offset(generator, try_end);

    if (try_block->exit != NULL) {
        ctx->try_exit_label = try_block->exit->label;

        njs_debug_generator(vm, "TRY   CTX    %p EXIT LABEL %V", ctx,
                            &ctx->try_exit_label);

        njs_generate_patch_block(vm, generator, try_block,
                                 NJS_GENERATOR_EXIT);

        njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break,
                          NJS_VMCODE_TRY_BREAK, NULL);
        try_break->exit_value = exit_index;

        try_break->offset = -sizeof(njs_vmcode_try_end_t);

    } else {
        try_break = NULL;
    }

    if (try_block->continuation != NULL) {
        ctx->try_cont_label = try_block->continuation->label;

        njs_generate_patch_block(vm, generator, try_block,
                                 NJS_GENERATOR_CONTINUATION);

        njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue,
                          NJS_VMCODE_TRY_CONTINUE, NULL);
        try_continue->exit_value = exit_index;

        try_continue->offset = -sizeof(njs_vmcode_try_end_t);

        if (try_break != NULL) {
            try_continue->offset -= sizeof(njs_vmcode_try_trampoline_t);
        }
    }

    njs_debug_generator(vm, "EXIT  %s %p",
                        njs_block_type(generator->block->type),
                        generator->block);

    generator->block = try_block->next;

    njs_code_set_jump_offset(generator, njs_vmcode_try_start_t,
                             ctx->try_offset);
    ctx->try_offset = try_end_offset;

    node = node->right;

    if (node->token_type == NJS_TOKEN_CATCH) {
        /* A "try/catch" case. */

        var = njs_variable_reference(vm, node->left);
        if (njs_slow_path(var == NULL)) {
            return NJS_ERROR;
        }

        catch_index = node->left->index;

        njs_generate_code_catch(generator, catch, catch_index, node);

        njs_generator_next(generator, njs_generate, node->right);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_try_catch, ctx, 0);
    }

    if (node->left != NULL) {
        /* A try/catch/finally case. */

        var = njs_variable_reference(vm, node->left->left);
        if (njs_slow_path(var == NULL)) {
            return NJS_ERROR;
        }

        catch_index = node->left->left->index;

        njs_generate_code_catch(generator, catch, catch_index, node);
        ctx->catch_offset = njs_code_offset(generator, catch);

        ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_TRY,
                                       &no_label);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

        ctx->catch_block = generator->block;
        ctx->catch_block->index = exit_index;

        njs_generator_next(generator, njs_generate, node->left->right);

        return njs_generator_after(vm, generator,
                                   njs_queue_first(&generator->stack), node,
                                   njs_generate_try_finally, ctx, 0);
    }

    /* A try/finally case. */

    njs_generate_code_catch(generator, catch, ctx->exception_index, NULL);

    ctx->catch_block = NULL;

    njs_code_set_jump_offset(generator, njs_vmcode_try_end_t,
                             ctx->try_offset);

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_try_end, ctx, 0);
}


static njs_int_t
njs_generate_try_catch(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                ret;
    njs_index_t              exit_index;
    njs_vmcode_finally_t     *finally;
    njs_generator_patch_t    *patch;
    njs_generator_block_t    *block, *try_block;
    njs_generator_try_ctx_t  *ctx;

    ctx = generator->context;

    try_block = ctx->try_block;
    exit_index = try_block->index;

    njs_code_set_jump_offset(generator, njs_vmcode_try_end_t,
                             ctx->try_offset);

    if (try_block->continuation != NULL || try_block->exit != NULL) {
        njs_generate_code_finally(generator, finally, ctx->exception_index,
                                  exit_index, NULL);

        if (try_block->continuation != NULL) {
            /*
             * block != NULL is checked
             * by njs_generate_continue_statement()
             */
            block = njs_generate_find_block(vm, generator->block,
                                            NJS_GENERATOR_LOOP,
                                            &ctx->try_cont_label);

            patch = njs_generate_make_continuation_patch(vm, block,
                                                         &ctx->try_cont_label,
                        njs_code_offset(generator, finally)
                         + offsetof(njs_vmcode_finally_t, continue_offset));
            if (njs_slow_path(patch == NULL)) {
                return NJS_ERROR;
            }
        }

        if (try_block->exit != NULL) {
            block = njs_generate_find_block(vm, generator->block,
                                            NJS_GENERATOR_ALL,
                                            &ctx->try_exit_label);

            /*
             * block can be NULL when &ctx->try_exit_label is "@return"
             * for outermost try-catch block.
             */

            if (block != NULL) {
                patch = njs_generate_make_exit_patch(vm, block,
                                                     &ctx->try_exit_label,
                            njs_code_offset(generator, finally)
                            + offsetof(njs_vmcode_finally_t, break_offset));
                if (njs_slow_path(patch == NULL)) {
                    return NJS_ERROR;
                }

            } else {

                /*
                 * when block == NULL, we still want to patch the "finally"
                 * instruction break_offset.
                 */

                block = njs_generate_find_block(vm, generator->block,
                                                NJS_GENERATOR_ALL,
                                                &no_label);

                if (block != NULL) {
                    patch = njs_generate_make_exit_patch(vm, block, &no_label,
                                njs_code_offset(generator, finally)
                                + offsetof(njs_vmcode_finally_t, break_offset));
                    if (njs_slow_path(patch == NULL)) {
                        return NJS_ERROR;
                    }
                }
            }
        }
    }

    /* TODO: release exception variable index. */

    ret = njs_generate_index_release(vm, generator, ctx->exception_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_try_finally(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t                  exit_index;
    njs_jump_off_t               catch_end_offset;
    njs_vmcode_catch_t           *catch;
    njs_vmcode_try_end_t         *catch_end;
    njs_generator_block_t        *try_block, *catch_block;
    njs_generator_try_ctx_t      *ctx;
    njs_vmcode_try_trampoline_t  *try_break, *try_continue;

    ctx = generator->context;

    try_block = ctx->try_block;
    exit_index = try_block->index;
    catch_block = ctx->catch_block;

    njs_generate_code(generator, njs_vmcode_try_end_t, catch_end,
                      NJS_VMCODE_TRY_END, node->left->right);
    catch_end_offset = njs_code_offset(generator, catch_end);

    if (catch_block->exit != NULL) {
        ctx->catch_exit_label = catch_block->exit->label;

        njs_generate_patch_block(vm, generator, catch_block,
                                 NJS_GENERATOR_EXIT);

        njs_generate_code(generator, njs_vmcode_try_trampoline_t,
                          try_break, NJS_VMCODE_TRY_BREAK, NULL);

        try_break->exit_value = exit_index;

        try_break->offset = -sizeof(njs_vmcode_try_end_t);

    } else {
        try_break = NULL;
    }

    if (catch_block->continuation != NULL) {
        ctx->catch_cont_label = catch_block->continuation->label;

        njs_generate_patch_block(vm, generator, catch_block,
                                 NJS_GENERATOR_CONTINUATION);

        njs_generate_code(generator, njs_vmcode_try_trampoline_t,
                          try_continue, NJS_VMCODE_TRY_CONTINUE,
                          NULL);

        try_continue->exit_value = exit_index;

        try_continue->offset = -sizeof(njs_vmcode_try_end_t);

        if (try_break != NULL) {
            try_continue->offset -= sizeof(njs_vmcode_try_trampoline_t);
        }
    }

    njs_debug_generator(vm, "EXIT  %s %p",
                        njs_block_type(generator->block->type),
                        generator->block);

    generator->block = catch_block->next;

    njs_code_set_jump_offset(generator, njs_vmcode_catch_t,
                             ctx->catch_offset);

    /* TODO: release exception variable index. */

    njs_generate_code_catch(generator, catch, ctx->exception_index, NULL);

    njs_code_set_jump_offset(generator, njs_vmcode_try_end_t,
                             catch_end_offset);

    njs_code_set_jump_offset(generator, njs_vmcode_try_end_t,
                             ctx->try_offset);

    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_try_end, ctx, 0);
}


static njs_int_t
njs_generate_try_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t                ret;
    njs_index_t              exit_index;
    const njs_str_t          *dest_label;
    njs_vmcode_finally_t     *finally;
    njs_generator_patch_t    *patch;
    njs_generator_block_t    *block, *try_block, *catch_block;
    njs_generator_try_ctx_t  *ctx;

    ctx = generator->context;

    try_block = ctx->try_block;
    exit_index = try_block->index;
    catch_block = ctx->catch_block;

    njs_generate_code_finally(generator, finally, ctx->exception_index,
                              exit_index, node);

    if (try_block->continuation != NULL
        || (catch_block && catch_block->continuation != NULL))
    {
        dest_label = njs_generate_jump_destination(vm, generator->block,
                                                   "try continue",
                                                   NJS_GENERATOR_LOOP,
                                                   &ctx->try_cont_label,
                                                   &ctx->catch_cont_label);
        if (njs_slow_path(dest_label == NULL)) {
            return NJS_ERROR;
        }

        /*
         * block != NULL is checked
         * by njs_generate_continue_statement()
         */
        block = njs_generate_find_block(vm, generator->block,
                                        NJS_GENERATOR_LOOP, dest_label);

        patch = njs_generate_make_continuation_patch(vm, block, dest_label,
                         njs_code_offset(generator, finally)
                         + offsetof(njs_vmcode_finally_t, continue_offset));
        if (njs_slow_path(patch == NULL)) {
            return NJS_ERROR;
        }
    }

    if (try_block->exit != NULL
        || (catch_block != NULL && catch_block->exit != NULL))
    {
        dest_label = njs_generate_jump_destination(vm, generator->block,
                                                   "try break/return",
                                                   NJS_GENERATOR_ALL
                                                   | NJS_GENERATOR_TRY,
                                                   &ctx->try_exit_label,
                                                   &ctx->catch_exit_label);
        if (njs_slow_path(dest_label == NULL)) {
            return NJS_ERROR;
        }

        /*
         * block can be NULL for "return" instruction in
         * outermost try-catch block.
         */
        block = njs_generate_find_block(vm, generator->block,
                                        NJS_GENERATOR_ALL, dest_label);
        if (block != NULL) {
            patch = njs_generate_make_exit_patch(vm, block, dest_label,
                            njs_code_offset(generator, finally)
                            + offsetof(njs_vmcode_finally_t, break_offset));
            if (njs_slow_path(patch == NULL)) {
                return NJS_ERROR;
            }

        } else {

            block = njs_generate_find_block(vm, generator->block,
                                            NJS_GENERATOR_ALL, &no_label);
            if (block != NULL) {
                patch = njs_generate_make_exit_patch(vm, block, &no_label,
                                njs_code_offset(generator, finally)
                                + offsetof(njs_vmcode_finally_t, break_offset));
                if (njs_slow_path(patch == NULL)) {
                    return NJS_ERROR;
                }
            }
        }
    }

    ret = njs_generate_index_release(vm, generator, ctx->exception_index);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    return njs_generator_stack_pop(vm, generator, ctx);
}


static njs_int_t
njs_generate_throw_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_throw_end, NULL, 0);
}

static njs_int_t
njs_generate_throw_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_vmcode_throw_t  *throw;

    njs_generate_code(generator, njs_vmcode_throw_t, throw,
                      NJS_VMCODE_THROW, node);

    node->index = node->right->index;
    throw->retval = node->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_import_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_variable_t       *var;
    njs_parser_node_t    *lvalue;
    njs_vmcode_import_t  *import;

    lvalue = node->left;

    var = njs_variable_reference(vm, lvalue);
    if (njs_slow_path(var == NULL)) {
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_import_t, import,
                      NJS_VMCODE_IMPORT, node);

    import->module = node->u.module;
    import->retval = lvalue->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_export_statement(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_export_statement_end, NULL, 0);
}


static njs_int_t
njs_generate_export_statement_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_node_t    *obj;
    njs_vmcode_return_t  *code;

    obj = node->right;

    njs_generate_code(generator, njs_vmcode_return_t, code,
                      NJS_VMCODE_RETURN, NULL);
    code->retval = obj->index;
    node->index = obj->index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_await(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_generator_next(generator, njs_generate, node->right);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_await_end, NULL, 0);
}


static njs_int_t
njs_generate_await_end(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t         index;
    njs_vmcode_await_t  *code;

    index = node->right->index;

    if (njs_slow_path(index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_await_t, code,
                      NJS_VMCODE_AWAIT, node);
    code->retval = index;
    node->index = index;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_wo_dest(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_scope_t  *scope;

    scope = njs_function_scope(node->scope);

    scope->dest_disable = 1;

    njs_generator_next(generator, njs_generate, node);

    return njs_generator_after(vm, generator,
                               njs_queue_first(&generator->stack), node,
                               njs_generate_wo_dest_after, NULL, 0);
}


static njs_int_t
njs_generate_wo_dest_after(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_parser_scope_t  *scope;

    scope = njs_function_scope(node->scope);

    scope->dest_disable = 0;

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_global_reference(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node, njs_bool_t exception)
{
    njs_index_t            index;
    njs_value_t            property;
    njs_vmcode_prop_get_t  *prop_get;

    index = njs_generate_temp_index_get(vm, generator, node);
    if (njs_slow_path(index == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_prop_get_t, prop_get,
                 exception ? NJS_VMCODE_GLOBAL_GET: NJS_VMCODE_PROPERTY_GET,
                 node);

    prop_get->value = index;

    prop_get->object = njs_scope_global_this_index();
    if (njs_slow_path(prop_get->object == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    njs_atom_to_value(vm, &property, node->u.reference.atom_id);

    prop_get->property = njs_scope_global_index(vm, &property,
                                                generator->runtime);
    if (njs_slow_path(prop_get->property == NJS_INDEX_ERROR)) {
        return NJS_ERROR;
    }

    node->index = index;

    if (!exception) {
        return NJS_OK;
    }

    return njs_generate_reference_error(vm, generator, node);
}


static njs_int_t
njs_generate_reference_error(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_str_t           entry;
    njs_vmcode_error_t  *ref_err;

    if (njs_slow_path(!node->u.reference.not_defined)) {
        njs_internal_error(vm, "variable is not defined but not_defined "
                               "is not set");
        return NJS_ERROR;
    }

    njs_generate_code(generator, njs_vmcode_error_t, ref_err, NJS_VMCODE_ERROR,
                      NULL);

    ref_err->type = NJS_OBJ_TYPE_REF_ERROR;

    njs_atom_string_get(vm, node->u.reference.atom_id, &entry);

    return njs_name_copy(vm, &ref_err->u.name, &entry);
}


static njs_index_t
njs_generate_dest_index(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t         ret;
    njs_parser_node_t   *dest;
    njs_parser_scope_t  *scope;

    ret = njs_generate_children_indexes_release(vm, generator, node);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    dest = node->dest;

    if (dest != NULL && dest->index != NJS_INDEX_NONE) {
        scope = njs_function_scope(node->scope);

        if (!scope->dest_disable) {
            return dest->index;
        }
    }

    return njs_generate_node_temp_index_get(vm, generator, node);
}


static njs_index_t
njs_generate_object_dest_index(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_index_t        index;
    njs_parser_node_t  *dest;

    dest = node->dest;

    if (dest != NULL && dest->index != NJS_INDEX_NONE) {
        index = dest->index;

        if (node->left == NULL) {
            /* Assign empty object directly to variable */
            return index;
        }
    }

    return njs_generate_node_temp_index_get(vm, generator, node);
}


static njs_index_t
njs_generate_node_temp_index_get(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    node->temporary = 1;

    node->index = njs_generate_temp_index_get(vm, generator, node);

    return node->index;
}


static njs_index_t
njs_generate_temp_index_get(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_arr_t           *cache;
    njs_index_t         *last;
    njs_parser_scope_t  *scope;

    cache = generator->index_cache;

    if (cache != NULL && cache->items != 0) {
        last = njs_arr_remove_last(cache);

        njs_debug_generator(vm, "INDEX REUSE  %04Xz", (size_t) *last);

        return *last;
    }

    scope = njs_function_scope(node->scope);
    if (njs_slow_path(scope == NULL)) {
        return NJS_ERROR;
    }

    return njs_scope_index(scope->type, scope->items++, NJS_LEVEL_LOCAL,
                           NJS_VARIABLE_VAR);
}


static njs_int_t
njs_generate_children_indexes_release(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    ret = njs_generate_node_index_release(vm, generator, node->left);

    if (njs_fast_path(ret == NJS_OK)) {
        return njs_generate_node_index_release(vm, generator, node->right);
    }

    return ret;
}


static njs_int_t
njs_generate_node_index_release(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    if (node != NULL && node->temporary) {
        if (node->hoist) {
            node->hoist = 0;
            return NJS_OK;
        }

        return njs_generate_index_release(vm, generator, node->index);
    }

    return NJS_OK;
}

static njs_int_t
njs_generate_node_index_release_pop(njs_vm_t *vm, njs_generator_t *generator,
    njs_parser_node_t *node)
{
    njs_int_t  ret;

    if (node != NULL && node->temporary) {
        ret = njs_generate_index_release(vm, generator, node->index);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    return njs_generator_stack_pop(vm, generator, NULL);
}


static njs_int_t
njs_generate_index_release(njs_vm_t *vm, njs_generator_t *generator,
    njs_index_t index)
{
    njs_arr_t    *cache;
    njs_index_t  *last;

    njs_debug_generator(vm, "INDEX RELEASE %04Xz", (size_t) index);

    cache = generator->index_cache;

    if (cache == NULL) {
        cache = njs_arr_create(vm->mem_pool, 4, sizeof(njs_value_t *));
        if (njs_slow_path(cache == NULL)) {
            return NJS_ERROR;
        }

        generator->index_cache = cache;
    }

    last = njs_arr_add(cache);
    if (njs_fast_path(last != NULL)) {
        *last = index;
        return NJS_OK;
    }

    return NJS_ERROR;
}
