/* Copyright 2015 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "jsp-label.h"
#include "lexer.h"
#include "opcodes-dumper.h"

/** \addtogroup jsparser ECMAScript parser
 * @{
 *
 * \addtogroup labels Jump labels
 * @{
 */

/**
 * Stack, containing current label set
 */
jsp_label_t *label_set_p = NULL;

/**
 * Initialize jumps labels mechanism
 */
void
jsp_label_init (void)
{
  JERRY_ASSERT (label_set_p == NULL);
} /* jsp_label_init */

/**
 * Finalize jumps labels mechanism
 */
void
jsp_label_finalize (void)
{
  JERRY_ASSERT (label_set_p == NULL);
} /* jsp_label_finalize */

/**
 * Remove all labels
 *
 * Note:
 *      should be used only upon a SyntaxError is raised
 */
void
jsp_label_remove_all_labels (void)
{
  label_set_p = NULL;
} /* jsp_label_remove_all_labels */

/**
 * Add label to the current label set
 */
void
jsp_label_push (jsp_label_t *out_label_p, /**< out: place where label structure
                                           *        should be initialized and
                                           *        linked into label set stack */
                jsp_label_type_flag_t type_mask, /**< label's type mask */
                token id) /**< identifier of the label (TOK_NAME)
                           *   if mask includes JSP_LABEL_TYPE_NAMED,
                           *   or empty token - otherwise */
{
  JERRY_ASSERT (out_label_p != NULL);

  if (type_mask & JSP_LABEL_TYPE_NAMED)
  {
    JERRY_ASSERT (id.type == TOK_NAME);

    JERRY_ASSERT (jsp_label_find (JSP_LABEL_TYPE_NAMED, id, NULL) == NULL);
  }
  else
  {
    JERRY_ASSERT (id.type == TOK_EMPTY);
  }

  out_label_p->type_mask = type_mask;
  out_label_p->id = id;
  out_label_p->continue_tgt_oc = MAX_OPCODES;
  out_label_p->breaks_list_oc = MAX_OPCODES;
  out_label_p->breaks_number = 0;
  out_label_p->continues_list_oc = MAX_OPCODES;
  out_label_p->continues_number = 0;
  out_label_p->next_label_p = label_set_p;
  out_label_p->is_nested_jumpable_border = false;

  label_set_p = out_label_p;
} /* jsp_label_push */

/**
 * Rewrite jumps to the label, if there any,
 * and remove it from the current label set
 *
 * @return the label should be on top of label set stack
 */
void
jsp_label_rewrite_jumps_and_pop (jsp_label_t *label_p, /**< label to remove (should be on top of stack) */
                                 opcode_counter_t break_tgt_oc) /**< target opcode counter
                                                                 *  for breaks on the label */
{
  JERRY_ASSERT (label_p != NULL);
  JERRY_ASSERT (break_tgt_oc != MAX_OPCODES);
  JERRY_ASSERT (label_set_p == label_p);

  /* Iterating jumps list, rewriting them */
  while (label_p->breaks_number--)
  {
    JERRY_ASSERT (label_p->breaks_list_oc != MAX_OPCODES);

    label_p->breaks_list_oc = rewrite_simple_or_nested_jump_and_get_next (label_p->breaks_list_oc,
                                                                          break_tgt_oc);
  }
  while (label_p->continues_number--)
  {
    JERRY_ASSERT (label_p->continue_tgt_oc != MAX_OPCODES);
    JERRY_ASSERT (label_p->continues_list_oc != MAX_OPCODES);

    label_p->continues_list_oc = rewrite_simple_or_nested_jump_and_get_next (label_p->continues_list_oc,
                                                                             label_p->continue_tgt_oc);
  }

  label_set_p = label_set_p->next_label_p;
} /* jsp_label_rewrite_jumps_and_pop */

/**
 * Find label with specified identifier
 *
 * @return if found, pointer to label descriptor,
 *         otherwise - NULL.
 */
jsp_label_t*
jsp_label_find (jsp_label_type_flag_t type_mask, /**< types to search for */
                token id, /**< identifier of the label (TOK_NAME)
                           *   if mask equals to JSP_LABEL_TYPE_NAMED,
                           *   or empty token - otherwise
                           *   (if so, mask should not include JSP_LABEL_TYPE_NAMED) */
                bool *out_is_simply_jumpable_p) /**< out: is the label currently
                                                 *        accessible with a simple jump */
{
  bool is_search_named = (type_mask == JSP_LABEL_TYPE_NAMED);

  if (is_search_named)
  {
    JERRY_ASSERT (id.type == TOK_NAME);
  }
  else
  {
    JERRY_ASSERT (!(type_mask & JSP_LABEL_TYPE_NAMED));
    JERRY_ASSERT (id.type == TOK_EMPTY);
  }

  bool is_simply_jumpable = true;
  jsp_label_t *ret_label_p = NULL;

  for (jsp_label_t *label_iter_p = label_set_p;
       label_iter_p != NULL;
       label_iter_p = label_iter_p->next_label_p)
  {
    if (label_iter_p->is_nested_jumpable_border)
    {
      is_simply_jumpable = false;
    }

    bool is_named_label = (label_iter_p->type_mask & JSP_LABEL_TYPE_NAMED);
    if ((is_search_named
         && is_named_label
         && lexer_are_tokens_with_same_identifier (label_iter_p->id, id))
        || (!is_search_named
            && (type_mask & label_iter_p->type_mask)))
    {
      ret_label_p = label_iter_p;

      break;
    }
  }

  if (out_is_simply_jumpable_p != NULL)
  {
    *out_is_simply_jumpable_p = is_simply_jumpable;
  }

  return ret_label_p;
} /* jsp_label_find */

/**
 * Dump jump and register it in the specified label to be rewritten later (see also: jsp_label_rewrite_jumps_and_pop)
 *
 * Warning:
 *         The dumped instruction should not be modified before it is rewritten, as its idx fields are used
 *         to link jump instructions related to the label into singly linked list.
 */
void
jsp_label_add_jump (jsp_label_t *label_p, /**< label to register jump for */
                    bool is_simply_jumpable, /**< is the label currently
                                              *    accessible with a simple jump */
                    bool is_break) /**< type of jump - 'break' (true) or 'continue' (false) */
{
  JERRY_ASSERT (label_p != NULL);

  if (is_break)
  {
    label_p->breaks_list_oc = dump_simple_or_nested_jump_for_rewrite (is_simply_jumpable,
                                                                      label_p->breaks_list_oc);
    label_p->breaks_number++;
  }
  else
  {
    label_p->continues_list_oc = dump_simple_or_nested_jump_for_rewrite (is_simply_jumpable,
                                                                         label_p->continues_list_oc);
    label_p->continues_number++;
  }
} /* jsp_label_add_jump */

/**
 * Setup target for 'continue' jumps,
 * associated with the labels, from innermost
 * to the specified label.
 */
void
jsp_label_setup_continue_target (jsp_label_t *outermost_label_p, /**< the outermost label to setup target for */
                                 opcode_counter_t tgt_oc) /**< target */
{
  /* There are no labels that could not be targeted with 'break' jumps */
  JERRY_ASSERT (tgt_oc != MAX_OPCODES);
  JERRY_ASSERT (outermost_label_p != NULL);

  for (jsp_label_t *label_iter_p = label_set_p;
       label_iter_p != outermost_label_p->next_label_p;
       label_iter_p = label_iter_p->next_label_p)
  {
    JERRY_ASSERT (label_iter_p != NULL);
    JERRY_ASSERT (label_iter_p->continue_tgt_oc == MAX_OPCODES);

    label_iter_p->continue_tgt_oc = tgt_oc;
  }
} /* jsp_label_setup_continue_target */

/**
 * Mark current label, if any, as nested jumpable border
 */
void
jsp_label_raise_nested_jumpable_border (void)
{
  if (label_set_p != NULL)
  {
    JERRY_ASSERT (!label_set_p->is_nested_jumpable_border);
    label_set_p->is_nested_jumpable_border = true;
  }
} /* jsp_label_raise_nested_jumpable_border */

/**
 * Unmark current label, if any, as nested jumpable border
 */
void
jsp_label_remove_nested_jumpable_border (void)
{
  if (label_set_p != NULL)
  {
    JERRY_ASSERT (label_set_p->is_nested_jumpable_border);
    label_set_p->is_nested_jumpable_border = false;
  }
} /* jsp_label_remove_nested_jumpable_border */

/**
 * Mask current label set to restore it later, and start new label set
 *
 * @return pointer to masked label set's list of labels
 */
jsp_label_t*
jsp_label_mask_set (void)
{
  jsp_label_t *ret_p = label_set_p;

  label_set_p = NULL;

  return ret_p;
} /* jsp_label_mask_set */

/**
 * Restore previously masked label set
 *
 * Note:
 *      current label set should be empty
 */
void
jsp_label_restore_set (jsp_label_t *masked_label_set_list_p) /**< list of labels of
                                                              *   a masked label set */
{
  JERRY_ASSERT (label_set_p == NULL);

  label_set_p = masked_label_set_list_p;
} /* jsp_label_restore_set */

/**
 * @}
 * @}
 */
