scanner_is_context_needed (parser_context_t *context_p, /**< context */
                           parser_check_context_type_t check_type) /**< context type */
{
  scanner_info_t *info_p = context_p->next_scanner_info_p;
  const uint8_t *data_p = (const uint8_t *) (info_p + 1);

  JERRY_UNUSED (check_type);

#if JERRY_ESNEXT
  JERRY_ASSERT ((check_type == PARSER_CHECK_BLOCK_CONTEXT ? info_p->type == SCANNER_TYPE_BLOCK
                                                          : info_p->type == SCANNER_TYPE_FUNCTION));

  uint32_t scope_stack_reg_top = (check_type != PARSER_CHECK_GLOBAL_CONTEXT ? context_p->scope_stack_reg_top
                                                                            : 0);
#else /* !JERRY_ESNEXT */
  JERRY_ASSERT (check_type == PARSER_CHECK_BLOCK_CONTEXT);
  JERRY_ASSERT (info_p->type == SCANNER_TYPE_BLOCK);

  uint32_t scope_stack_reg_top = context_p->scope_stack_reg_top;
#endif /* !JERRY_NDEBUG */

  while (data_p[0] != SCANNER_STREAM_TYPE_END)
  {
    uint8_t data = data_p[0];

#if JERRY_ESNEXT
    uint32_t type = data & SCANNER_STREAM_TYPE_MASK;

    if (JERRY_UNLIKELY (check_type == PARSER_CHECK_FUNCTION_CONTEXT))
    {
      if (JERRY_UNLIKELY (type == SCANNER_STREAM_TYPE_HOLE))
      {
        data_p++;
        continue;
      }

      if (JERRY_UNLIKELY (SCANNER_STREAM_TYPE_IS_ARGUMENTS (type)))
      {
        if ((data & SCANNER_STREAM_NO_REG)
            || scope_stack_reg_top >= PARSER_MAXIMUM_NUMBER_OF_REGISTERS)
        {
          return true;
        }

        scope_stack_reg_top++;
        data_p++;
        continue;
      }
    }

#ifndef JERRY_NDEBUG
    if (check_type == PARSER_CHECK_BLOCK_CONTEXT)
    {
      JERRY_ASSERT (type == SCANNER_STREAM_TYPE_VAR
                    || type == SCANNER_STREAM_TYPE_LET
                    || type == SCANNER_STREAM_TYPE_CONST
                    || type == SCANNER_STREAM_TYPE_LOCAL
                    || type == SCANNER_STREAM_TYPE_FUNC);
    }
    else if (check_type == PARSER_CHECK_GLOBAL_CONTEXT)
    {
#if JERRY_MODULE_SYSTEM
      const bool is_import = (type == SCANNER_STREAM_TYPE_IMPORT);
#else
      const bool is_import = true;
#endif /* JERRY_MODULE_SYSTEM */

      /* FIXME: a private declarative lexical environment should always be present
       * for modules. Remove SCANNER_STREAM_TYPE_IMPORT after it is implemented. */
      JERRY_ASSERT (type == SCANNER_STREAM_TYPE_VAR
                    || type == SCANNER_STREAM_TYPE_LET
                    || type == SCANNER_STREAM_TYPE_CONST
                    || type == SCANNER_STREAM_TYPE_FUNC
                    || is_import);

      /* Only let/const can be stored in registers */
      JERRY_ASSERT ((data & SCANNER_STREAM_NO_REG)
                    || (type == SCANNER_STREAM_TYPE_FUNC && (context_p->global_status_flags & ECMA_PARSE_DIRECT_EVAL))
                    || type == SCANNER_STREAM_TYPE_LET
                    || type == SCANNER_STREAM_TYPE_CONST);
    }
    else
    {
      JERRY_ASSERT (check_type == PARSER_CHECK_FUNCTION_CONTEXT);

      JERRY_ASSERT (type == SCANNER_STREAM_TYPE_VAR
                    || type == SCANNER_STREAM_TYPE_LET
                    || type == SCANNER_STREAM_TYPE_CONST
                    || type == SCANNER_STREAM_TYPE_LOCAL
                    || type == SCANNER_STREAM_TYPE_ARG
                    || type == SCANNER_STREAM_TYPE_ARG_VAR
                    || type == SCANNER_STREAM_TYPE_DESTRUCTURED_ARG
                    || type == SCANNER_STREAM_TYPE_DESTRUCTURED_ARG_VAR
                    || type == SCANNER_STREAM_TYPE_ARG_FUNC
                    || type == SCANNER_STREAM_TYPE_DESTRUCTURED_ARG_FUNC
                    || type == SCANNER_STREAM_TYPE_FUNC);
    }
#endif /* !JERRY_NDEBUG */

#else /* !JERRY_ESNEXT */
    JERRY_ASSERT ((data & SCANNER_STREAM_TYPE_MASK) == SCANNER_STREAM_TYPE_VAR);
#endif /* JERRY_ESNEXT */

    if (!(data & SCANNER_STREAM_UINT16_DIFF))
    {
      if (data_p[2] != 0)
      {
        data_p += 2 + 1;
      }
      else
      {
        data_p += 2 + 1 + sizeof (const uint8_t *);
      }
    }
    else
    {
      data_p += 2 + 2;
    }

#if JERRY_ESNEXT
#if JERRY_MODULE_SYSTEM
    const bool is_import = (type == SCANNER_STREAM_TYPE_IMPORT);
#else /* !JERRY_MODULE_SYSTEM */
    const bool is_import = false;
#endif /* JERRY_MODULE_SYSTEM */

    if (JERRY_UNLIKELY (check_type == PARSER_CHECK_GLOBAL_CONTEXT)
        && (type == SCANNER_STREAM_TYPE_VAR
            || (type == SCANNER_STREAM_TYPE_FUNC && !(context_p->global_status_flags & ECMA_PARSE_EVAL))
            || is_import))
    {
      continue;
    }

    if (JERRY_UNLIKELY (check_type == PARSER_CHECK_FUNCTION_CONTEXT))
    {
      if (SCANNER_STREAM_TYPE_IS_ARG_FUNC (type)
          || type == SCANNER_STREAM_TYPE_ARG_VAR
          || type == SCANNER_STREAM_TYPE_DESTRUCTURED_ARG_VAR)
      {
        /* The return value is true, if the variable is stored in the lexical environment
         * or all registers have already been used for function arguments. This can be
         * inprecise in the latter case, but this is a very rare corner case. A more
         * sophisticated check would require to decode the literal. */
        if ((data & SCANNER_STREAM_NO_REG)
            || scope_stack_reg_top >= PARSER_MAXIMUM_NUMBER_OF_REGISTERS)
        {
          return true;
        }
        continue;
      }

      if (SCANNER_STREAM_TYPE_IS_ARG (type))
      {
        continue;
      }
    }
#endif /* JERRY_ESNEXT */

    if ((data & SCANNER_STREAM_NO_REG)
        || scope_stack_reg_top >= PARSER_MAXIMUM_NUMBER_OF_REGISTERS)
    {
      return true;
    }

    scope_stack_reg_top++;
  }

  return false;
} /* scanner_is_context_needed */