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

#ifndef _NJS_SCOPE_H_INCLUDED_
#define _NJS_SCOPE_H_INCLUDED_

#define NJS_SCOPE_VAR_OFFSET    0
#define NJS_SCOPE_VAR_SIZE      4

#define NJS_SCOPE_TYPE_OFFSET   (NJS_SCOPE_VAR_OFFSET + NJS_SCOPE_VAR_SIZE)
#define NJS_SCOPE_TYPE_SIZE     4

#define NJS_SCOPE_VALUE_OFFSET  (NJS_SCOPE_TYPE_OFFSET + NJS_SCOPE_TYPE_SIZE)
#define NJS_SCOPE_VALUE_SIZE    24

#define NJS_SCOPE_VALUE_MASK    ((1 << NJS_SCOPE_VALUE_SIZE) - 1)
#define NJS_SCOPE_VAR_MASK      ((1 << NJS_SCOPE_VAR_SIZE) - 1)
#define NJS_SCOPE_TYPE_MASK     ((1 << NJS_SCOPE_TYPE_SIZE) - 1)

#define NJS_SCOPE_VALUE_MAX     NJS_SCOPE_VALUE_MASK

#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_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)
{
    njs_assert(type < NJS_LEVEL_MAX);
    njs_assert(scope == NJS_SCOPE_GLOBAL || scope == NJS_SCOPE_FUNCTION);

    if (index > NJS_SCOPE_VALUE_MAX) {
        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_TYPE_OFFSET)
            | var_type;
}


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


njs_inline njs_level_type_t
njs_scope_index_type(njs_index_t index)
{
    return (njs_level_type_t) ((index >> NJS_SCOPE_TYPE_OFFSET)
                               & 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_ */
