
/*
 * Copyright (C) Alexander Borisov
 * Copyright (C) NGINX, Inc.
 */

#ifndef _NJS_SCOPE_H_INCLUDED_
#define _NJS_SCOPE_H_INCLUDED_


#define NJS_SCOPE_VAR_SIZE      4
#define NJS_SCOPE_TYPE_OFFSET   (NJS_SCOPE_VAR_SIZE + 4)
#define NJS_SCOPE_VALUE_OFFSET  (NJS_SCOPE_TYPE_OFFSET + 1)
#define NJS_SCOPE_VALUE_MAX     ((1 << (32 - NJS_SCOPE_VALUE_OFFSET)) - 1)
#define NJS_SCOPE_TYPE_MASK     ((NJS_SCOPE_VALUE_MAX) << NJS_SCOPE_VAR_SIZE)

#define NJS_INDEX_NONE          ((njs_index_t) 0)
#define NJS_INDEX_ERROR         ((njs_index_t) -1)


njs_index_t njs_scope_temp_index(njs_parser_scope_t *scope);
njs_value_t *njs_scope_create_index_value(njs_vm_t *vm, njs_index_t index);
njs_value_t **njs_scope_make(njs_vm_t *vm, uint32_t count);
njs_index_t njs_scope_global_index(njs_vm_t *vm, const njs_value_t *src,
    njs_uint_t runtime);
njs_value_t *njs_scope_value_get(njs_vm_t *vm, njs_index_t index);


njs_inline njs_index_t
njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type,
                njs_variable_type_t var_type)
{
    if (index > NJS_SCOPE_VALUE_MAX || type >= NJS_LEVEL_MAX
        || (scope != NJS_SCOPE_GLOBAL && scope != NJS_SCOPE_FUNCTION))
    {
        return NJS_INDEX_ERROR;
    }

    if (scope == NJS_SCOPE_GLOBAL && type == NJS_LEVEL_LOCAL) {
        type = NJS_LEVEL_GLOBAL;
    }

    return (index << NJS_SCOPE_VALUE_OFFSET) | (type << NJS_SCOPE_VAR_SIZE)
            | var_type;
}


njs_inline njs_variable_type_t
njs_scope_index_var(njs_index_t index)
{
    return (njs_variable_type_t) (index & ~NJS_SCOPE_TYPE_MASK);
}


njs_inline njs_level_type_t
njs_scope_index_type(njs_index_t index)
{
    return (njs_level_type_t) ((index >> NJS_SCOPE_VAR_SIZE)
                               & ~NJS_SCOPE_TYPE_MASK);
}


njs_inline uint32_t
njs_scope_index_value(njs_index_t index)
{
    return (uint32_t) (index >> NJS_SCOPE_VALUE_OFFSET);
}


njs_inline njs_value_t *
njs_scope_value(njs_vm_t *vm, njs_index_t index)
{
    return vm->levels[njs_scope_index_type(index)]
                     [njs_scope_index_value(index)];
}


njs_inline njs_value_t *
njs_scope_valid_value(njs_vm_t *vm, njs_index_t index)
{
    njs_value_t  *value;

    value = njs_scope_value(vm, index);

    if (!njs_is_valid(value)) {
        if (njs_scope_index_var(index) <= NJS_VARIABLE_LET) {
            njs_reference_error(vm, "cannot access variable "
                                    "before initialization");
            return NULL;
        }

        njs_set_undefined(value);
    }

    return value;
}


njs_inline void
njs_scope_value_set(njs_vm_t *vm, njs_index_t index, njs_value_t *value)
{
    vm->levels[njs_scope_index_type(index)]
              [njs_scope_index_value(index)] = value;
}


njs_inline njs_value_t *
njs_scope_value_clone(njs_vm_t *vm, njs_index_t index, njs_value_t *value)
{
    njs_value_t  *newval;

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

    *newval = *value;

    njs_scope_value_set(vm, index, newval);

    return newval;
}


njs_inline njs_index_t
njs_scope_undefined_index(njs_vm_t *vm, njs_uint_t runtime)
{
    return njs_scope_global_index(vm, &njs_value_undefined, runtime);
}


njs_inline njs_index_t
njs_scope_global_this_index()
{
    return njs_scope_index(NJS_SCOPE_GLOBAL, 0, NJS_LEVEL_LOCAL,
                           NJS_VARIABLE_VAR);
}


#endif /* _NJS_PARSER_H_INCLUDED_ */
