/* Copyright 2014-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 "ecma-helpers.h"
#include "hash-table.h"
#include "jrt-libc-includes.h"
#include "jsp-label.h"
#include "jsp-mm.h"
#include "opcodes.h"
#include "opcodes-dumper.h"
#include "opcodes-native-call.h"
#include "parser.h"
#include "scopes-tree.h"
#include "serializer.h"
#include "stack.h"
#include "syntax-errors.h"
#include "vm.h"

/**
 * Flag, indicating whether result of expression
 * evaluation should be stored to 'eval result'
 * temporary variable.
 *
 * In other words, the flag indicates whether
 * 'eval result' store code should be dumped.
 *
 * See also:
 *          parse_expression
 */
typedef enum
{
  JSP_EVAL_RET_STORE_NOT_DUMP, /**< do not dump */
  JSP_EVAL_RET_STORE_DUMP, /**< dump */
} jsp_eval_ret_store_t;

static token tok;
static bool inside_eval = false;
static bool inside_function = false;
static bool parser_show_opcodes = false;

enum
{
  scopes_global_size
};
STATIC_STACK (scopes, scopes_tree)

#define EMIT_ERROR(MESSAGE) PARSE_ERROR(MESSAGE, tok.loc)
#define EMIT_SORRY(MESSAGE) PARSE_SORRY(MESSAGE, tok.loc)
#define EMIT_ERROR_VARG(MESSAGE, ...) PARSE_ERROR_VARG(MESSAGE, tok.loc, __VA_ARGS__)

#define OPCODE_IS(OP, ID) (OP.op_idx == __op__idx_##ID)

static operand parse_expression (bool, jsp_eval_ret_store_t);
static void parse_statement (jsp_label_t *outermost_stmt_label_p);
static operand parse_assignment_expression (bool);
static void parse_source_element_list (bool);
static operand parse_argument_list (varg_list_type, operand, uint8_t *, operand *);
static void process_keyword_names (void);
static void skip_braces (void);
static void skip_parens (void);

static bool
token_is (token_type tt)
{
  return tok.type == tt;
}

static uint16_t
token_data (void)
{
  return tok.uid;
}

/**
 * Get token data as `lit_cpointer_t`
 *
 * @return compressed pointer to token data
 */
static lit_cpointer_t
token_data_as_lit_cp (void)
{
  lit_cpointer_t cp;
  cp.packed_value = tok.uid;

  return cp;
} /* token_data_as_lit_cp */

static void
skip_token (void)
{
  tok = lexer_next_token ();
}

static void
assert_keyword (keyword kw)
{
  if (!token_is (TOK_KEYWORD) || token_data () != kw)
  {
    EMIT_ERROR_VARG ("Expected keyword '%s'", lexer_keyword_to_string (kw));
    JERRY_UNREACHABLE ();
  }
}

static bool
is_keyword (keyword kw)
{
  return token_is (TOK_KEYWORD) && token_data () == kw;
}

static void
current_token_must_be (token_type tt)
{
  if (!token_is (tt))
  {
    EMIT_ERROR_VARG ("Expected '%s' token", lexer_token_type_to_string (tt));
  }
}

static void
skip_newlines (void)
{
  do
  {
    skip_token ();
  }
  while (token_is (TOK_NEWLINE));
}

static void
next_token_must_be (token_type tt)
{
  skip_token ();
  if (!token_is (tt))
  {
    EMIT_ERROR_VARG ("Expected '%s' token", lexer_token_type_to_string (tt));
  }
}

static void
token_after_newlines_must_be (token_type tt)
{
  skip_newlines ();
  if (!token_is (tt))
  {
    EMIT_ERROR_VARG ("Expected '%s' token", lexer_token_type_to_string (tt));
  }
}

static void
token_after_newlines_must_be_keyword (keyword kw)
{
  skip_newlines ();
  if (!is_keyword (kw))
  {
    EMIT_ERROR_VARG ("Expected keyword '%s'", lexer_keyword_to_string (kw));
  }
}

static bool
is_strict_mode (void)
{
  return scopes_tree_strict_mode (STACK_TOP (scopes));
}

/* property_name
  : Identifier
  | Keyword
  | StringLiteral
  | NumericLiteral
  ;
*/
static operand
parse_property_name (void)
{
  switch (tok.type)
  {
    case TOK_NAME:
    case TOK_STRING:
    case TOK_NUMBER:
    {
      return literal_operand (token_data_as_lit_cp ());
    }
    case TOK_SMALL_INT:
    {
      literal_t lit = lit_find_or_create_literal_from_num ((ecma_number_t) token_data ());
      return literal_operand (lit_cpointer_t::compress (lit));
    }
    case TOK_KEYWORD:
    {
      const char *s = lexer_keyword_to_string ((keyword) token_data ());
      literal_t lit = lit_find_or_create_literal_from_charset ((const ecma_char_t *) s, (ecma_length_t) strlen (s));
      return literal_operand (lit_cpointer_t::compress (lit));
    }
    default:
    {
      EMIT_ERROR_VARG ("Wrong property name type: %s", lexer_token_type_to_string (tok.type));
    }
  }
}

/* property_name_and_value
  : property_name LT!* ':' LT!* assignment_expression
  ; */
static void
parse_property_name_and_value (void)
{
  const operand name = parse_property_name ();
  token_after_newlines_must_be (TOK_COLON);
  skip_newlines ();
  const operand value = parse_assignment_expression (true);
  dump_prop_name_and_value (name, value);
  syntax_add_prop_name (name, PROP_DATA);
}

/* property_assignment
  : property_name_and_value
  | get LT!* property_name LT!* '(' LT!* ')' LT!* '{' LT!* function_body LT!* '}'
  | set LT!* property_name LT!* '(' identifier ')' LT!* '{' LT!* function_body LT!* '}'
  ; */
static void
parse_property_assignment (void)
{
  if (token_is (TOK_NAME))
  {
    bool is_setter;

    if (lit_literal_equal_type_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()), (const ecma_char_t *) "get"))
    {
      is_setter = false;
    }
    else if (lit_literal_equal_type_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()), (const ecma_char_t *) "set"))
    {
      is_setter = true;
    }
    else
    {
      parse_property_name_and_value ();

      return;
    }

    const token temp = tok;
    skip_newlines ();
    if (token_is (TOK_COLON))
    {
      lexer_save_token (tok);
      tok = temp;

      parse_property_name_and_value ();

      return;
    }

    const operand name = parse_property_name ();
    syntax_add_prop_name (name, is_setter ? PROP_SET : PROP_GET);

    skip_newlines ();
    const operand func = parse_argument_list (VARG_FUNC_EXPR, name, NULL, NULL);

    dump_function_end_for_rewrite ();

    const bool is_strict = scopes_tree_strict_mode (STACK_TOP (scopes));
    scopes_tree_set_strict_mode (STACK_TOP (scopes), false);

    token_after_newlines_must_be (TOK_OPEN_BRACE);
    skip_newlines ();

    bool was_in_function = inside_function;
    inside_function = true;

    jsp_label_t *masked_label_set_p = jsp_label_mask_set ();

    parse_source_element_list (false);

    jsp_label_restore_set (masked_label_set_p);

    token_after_newlines_must_be (TOK_CLOSE_BRACE);

    scopes_tree_set_strict_mode (STACK_TOP (scopes), is_strict);

    dump_ret ();
    rewrite_function_end (VARG_FUNC_EXPR);

    inside_function = was_in_function;

    if (is_setter)
    {
      dump_prop_setter_decl (name, func);
    }
    else
    {
      dump_prop_getter_decl (name, func);
    }
  }
  else
  {
    parse_property_name_and_value ();
  }
}

/** Parse list of identifiers, assigment expressions or properties, splitted by comma.
    For each ALT dumps appropriate bytecode. Uses OBJ during dump if neccesary.
    Result tmp. */
static operand
parse_argument_list (varg_list_type vlt, operand obj, uint8_t *args_count, operand *this_arg_p)
{
  token_type close_tt = TOK_CLOSE_PAREN;
  uint8_t args_num = 0;

  JERRY_ASSERT (!(vlt != VARG_CALL_EXPR && this_arg_p != NULL));

  switch (vlt)
  {
    case VARG_FUNC_DECL:
    case VARG_FUNC_EXPR:
    {
      syntax_start_checking_of_vargs ();
      /* FALLTHRU */
    }
    case VARG_CONSTRUCT_EXPR:
    {
      current_token_must_be (TOK_OPEN_PAREN);
      dump_varg_header_for_rewrite (vlt, obj);
      break;
    }
    case VARG_CALL_EXPR:
    {
      current_token_must_be (TOK_OPEN_PAREN);
      if (dumper_is_intrinsic (obj))
      {
        break;
      }

      opcode_call_flags_t call_flags = OPCODE_CALL_FLAGS__EMPTY;

      operand this_arg = empty_operand ();
      if (this_arg_p != NULL
          && !operand_is_empty (*this_arg_p))
      {
        call_flags = (opcode_call_flags_t) (call_flags | OPCODE_CALL_FLAGS_HAVE_THIS_ARG);

        if (this_arg_p->type == OPERAND_LITERAL)
        {
          /*
           * FIXME:
           *       Base of CallExpression should be evaluated only once during evaluation of CallExpression
           *
           * See also:
           *          Evaluation of MemberExpression (ECMA-262 v5, 11.2.1)
           */
          this_arg = dump_variable_assignment_res (*this_arg_p);
        }
        else
        {
          this_arg = *this_arg_p;
        }

        /*
         * Presence of explicit 'this' argument implies that it is not Direct call to Eval
         *
         * See also:
         *          ECMA-262 v5, 15.2.2.1
         */
      }
      else if (dumper_is_eval_literal (obj))
      {
        call_flags = (opcode_call_flags_t) (call_flags | OPCODE_CALL_FLAGS_DIRECT_CALL_TO_EVAL_FORM);
      }

      dump_varg_header_for_rewrite (vlt, obj);

      if (call_flags != OPCODE_CALL_FLAGS__EMPTY)
      {
        if (call_flags & OPCODE_CALL_FLAGS_HAVE_THIS_ARG)
        {
          JERRY_ASSERT (!operand_is_empty (this_arg));
          dump_call_additional_info (call_flags, this_arg);
        }
        else
        {
          dump_call_additional_info (call_flags, empty_operand ());
        }
      }

      break;
    }
    case VARG_ARRAY_DECL:
    {
      current_token_must_be (TOK_OPEN_SQUARE);
      close_tt = TOK_CLOSE_SQUARE;
      dump_varg_header_for_rewrite (vlt, obj);
      break;
    }
    case VARG_OBJ_DECL:
    {
      current_token_must_be (TOK_OPEN_BRACE);
      close_tt = TOK_CLOSE_BRACE;
      dump_varg_header_for_rewrite (vlt, obj);
      syntax_start_checking_of_prop_names ();
      break;
    }
  }

  skip_newlines ();
  while (!token_is (close_tt))
  {
    operand op;
    switch (vlt)
    {
      case VARG_FUNC_DECL:
      case VARG_FUNC_EXPR:
      {
        current_token_must_be (TOK_NAME);
        op = literal_operand (token_data_as_lit_cp ());
        syntax_add_varg (op);
        syntax_check_for_eval_and_arguments_in_strict_mode (op, is_strict_mode (), tok.loc);
        break;
      }
      case VARG_ARRAY_DECL:
      {
        if (token_is (TOK_COMMA))
        {
          op = dump_undefined_assignment_res ();
          dump_varg (op);
          args_num++;
          skip_newlines ();
          continue;
        }
        /* FALLTHRU  */
      }
      case VARG_CONSTRUCT_EXPR:
      {
        op = parse_assignment_expression (true);
        break;
      }
      case VARG_CALL_EXPR:
      {
        op = parse_assignment_expression (true);
        if (dumper_is_intrinsic (obj))
        {
          operand res = dump_intrinsic (obj, op);
          token_after_newlines_must_be (close_tt);
          return res;
        }
        break;
      }
      case VARG_OBJ_DECL:
      {
        parse_property_assignment ();
        break;
      }
    }

    /* In case of obj_decl prop is already dumped.  */
    if (vlt != VARG_OBJ_DECL)
    {
      dump_varg (op);
    }
    args_num++;

    skip_newlines ();
    if (!token_is (TOK_COMMA))
    {
      current_token_must_be (close_tt);
      break;
    }

    skip_newlines ();
  }

  if (args_count != NULL)
  {
    *args_count = args_num;
  }

  operand res;
  switch (vlt)
  {
    case VARG_FUNC_DECL:
    case VARG_FUNC_EXPR:
    {
      syntax_check_for_syntax_errors_in_formal_param_list (is_strict_mode (), tok.loc);
      res = rewrite_varg_header_set_args_count (args_num);
      break;
    }
    case VARG_CONSTRUCT_EXPR:
    case VARG_ARRAY_DECL:
    case VARG_CALL_EXPR:
    {
      /* Intrinsics are already processed.  */
      res = rewrite_varg_header_set_args_count (args_num);
      break;
    }
    case VARG_OBJ_DECL:
    {
      syntax_check_for_duplication_of_prop_names (is_strict_mode (), tok.loc);
      res = rewrite_varg_header_set_args_count (args_num);
      break;
    }
  }
  return res;
}

/* function_declaration
  : 'function' LT!* Identifier LT!*
    '(' (LT!* Identifier (LT!* ',' LT!* Identifier)*) ? LT!* ')' LT!* function_body
  ;

   function_body
  : '{' LT!* sourceElements LT!* '}' */
static void
parse_function_declaration (void)
{
  STACK_DECLARE_USAGE (scopes);

  assert_keyword (KW_FUNCTION);

  jsp_label_t *masked_label_set_p = jsp_label_mask_set ();

  token_after_newlines_must_be (TOK_NAME);
  const operand name = literal_operand (token_data_as_lit_cp ());

  skip_newlines ();
  STACK_PUSH (scopes, scopes_tree_init (STACK_TOP (scopes)));
  serializer_set_scope (STACK_TOP (scopes));
  scopes_tree_set_strict_mode (STACK_TOP (scopes), scopes_tree_strict_mode (STACK_HEAD (scopes, 2)));
  lexer_set_strict_mode (scopes_tree_strict_mode (STACK_TOP (scopes)));
  parse_argument_list (VARG_FUNC_DECL, name, NULL, NULL);

  dump_function_end_for_rewrite ();

  token_after_newlines_must_be (TOK_OPEN_BRACE);
  skip_newlines ();

  bool was_in_function = inside_function;
  inside_function = true;

  parse_source_element_list (false);

  next_token_must_be (TOK_CLOSE_BRACE);

  dump_ret ();
  rewrite_function_end (VARG_FUNC_DECL);

  inside_function = was_in_function;

  STACK_DROP (scopes, 1);
  serializer_set_scope (STACK_TOP (scopes));
  lexer_set_strict_mode (scopes_tree_strict_mode (STACK_TOP (scopes)));

  jsp_label_restore_set (masked_label_set_p);

  STACK_CHECK_USAGE (scopes);
}

/* function_expression
  : 'function' LT!* Identifier? LT!* '(' formal_parameter_list? LT!* ')' LT!* function_body
  ; */
static operand
parse_function_expression (void)
{
  assert_keyword (KW_FUNCTION);

  operand res;

  skip_newlines ();
  if (token_is (TOK_NAME))
  {
    const operand name = literal_operand (token_data_as_lit_cp ());
    skip_newlines ();
    res = parse_argument_list (VARG_FUNC_EXPR, name, NULL, NULL);
  }
  else
  {
    lexer_save_token (tok);
    skip_newlines ();
    res = parse_argument_list (VARG_FUNC_EXPR, empty_operand (), NULL, NULL);
  }

  dump_function_end_for_rewrite ();

  token_after_newlines_must_be (TOK_OPEN_BRACE);
  skip_newlines ();

  bool was_in_function = inside_function;
  inside_function = true;

  jsp_label_t *masked_label_set_p = jsp_label_mask_set ();

  parse_source_element_list (false);

  jsp_label_restore_set (masked_label_set_p);


  next_token_must_be (TOK_CLOSE_BRACE);

  dump_ret ();
  rewrite_function_end (VARG_FUNC_EXPR);

  inside_function = was_in_function;

  return res;
}

/* array_literal
  : '[' LT!* assignment_expression? (LT!* ',' (LT!* assignment_expression)?)* LT!* ']' LT!*
  ; */
static operand
parse_array_literal (void)
{
  return parse_argument_list (VARG_ARRAY_DECL, empty_operand (), NULL, NULL);
}

/* object_literal
  : '{' LT!* property_assignment (LT!* ',' LT!* property_assignment)* LT!* '}'
  ; */
static operand
parse_object_literal (void)
{
  return parse_argument_list (VARG_OBJ_DECL, empty_operand (), NULL, NULL);
}

/* literal
  : 'null'
  | 'true'
  | 'false'
  | number_literal
  | string_literal
  ; */
static operand
parse_literal (void)
{
  switch (tok.type)
  {
    case TOK_NUMBER: return dump_number_assignment_res (token_data_as_lit_cp ());
    case TOK_STRING: return dump_string_assignment_res (token_data_as_lit_cp ());
    case TOK_NULL: return dump_null_assignment_res ();
    case TOK_BOOL: return dump_boolean_assignment_res ((bool) token_data ());
    case TOK_SMALL_INT: return dump_smallint_assignment_res ((idx_t) token_data ());
    default:
    {
      EMIT_ERROR ("Expected literal");
    }
  }
}

/* primary_expression
  : 'this'
  | Identifier
  | literal
  | 'undefined'
  | '[' LT!* array_literal LT!* ']'
  | '{' LT!* object_literal LT!* '}'
  | '(' LT!* expression LT!* ')'
  ; */
static operand
parse_primary_expression (void)
{
  if (is_keyword (KW_THIS))
  {
    return dump_this_res ();
  }

  switch (tok.type)
  {
    case TOK_NULL:
    case TOK_BOOL:
    case TOK_SMALL_INT:
    case TOK_NUMBER:
    case TOK_STRING: return parse_literal ();
    case TOK_NAME: return literal_operand (token_data_as_lit_cp ());
    case TOK_OPEN_SQUARE: return parse_array_literal ();
    case TOK_OPEN_BRACE: return parse_object_literal ();
    case TOK_OPEN_PAREN:
    {
      skip_newlines ();
      if (!token_is (TOK_CLOSE_PAREN))
      {
        operand res = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
        token_after_newlines_must_be (TOK_CLOSE_PAREN);
        return res;
      }
      /* FALLTHRU */
    }
    default:
    {
      EMIT_ERROR_VARG ("Unknown token %s", lexer_token_type_to_string (tok.type));
    }
  }
}

/* member_expression
  : (primary_expression | function_expression | 'new' LT!* member_expression (LT!* '(' LT!* arguments? LT!* ')')
      (LT!* member_expression_suffix)*
  ;

   arguments
  : assignment_expression (LT!* ',' LT!* assignment_expression)*)?
  ;

   member_expression_suffix
  : index_suffix
  | property_reference_suffix
  ;

   index_suffix
  : '[' LT!* expression LT!* ']'
  ;

   property_reference_suffix
  : '.' LT!* Identifier
  ; */
static operand
parse_member_expression (operand *this_arg, operand *prop_gl)
{
  operand expr;
  if (is_keyword (KW_FUNCTION))
  {
    expr = parse_function_expression ();
  }
  else if (is_keyword (KW_NEW))
  {
    skip_newlines ();
    expr = parse_member_expression (this_arg, prop_gl);

    skip_newlines ();
    if (token_is (TOK_OPEN_PAREN))
    {
      expr = parse_argument_list (VARG_CONSTRUCT_EXPR, expr, NULL, NULL);
    }
    else
    {
      lexer_save_token (tok);
      dump_varg_header_for_rewrite (VARG_CONSTRUCT_EXPR, expr);
      expr = rewrite_varg_header_set_args_count (0);
    }
  }
  else
  {
    expr = parse_primary_expression ();
  }

  skip_newlines ();
  while (token_is (TOK_OPEN_SQUARE) || token_is (TOK_DOT))
  {
    operand prop = empty_operand ();

    if (token_is (TOK_OPEN_SQUARE))
    {
      skip_newlines ();
      prop = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
      next_token_must_be (TOK_CLOSE_SQUARE);
    }
    else if (token_is (TOK_DOT))
    {
      skip_newlines ();
      if (token_is (TOK_NAME))
      {
        prop = dump_string_assignment_res (token_data_as_lit_cp ());
      }
      else if (token_is (TOK_KEYWORD))
      {
        const char *s = lexer_keyword_to_string ((keyword) token_data ());
        literal_t lit = lit_find_or_create_literal_from_charset ((const ecma_char_t *) s, (ecma_length_t) strlen (s));
        if (lit == NULL)
        {
          EMIT_ERROR ("Expected identifier");
        }
        prop = dump_string_assignment_res (lit_cpointer_t::compress (lit));
      }
      else
      {
        EMIT_ERROR ("Expected identifier");
      }
    }
    skip_newlines ();

    if (this_arg)
    {
      *this_arg = expr;
    }
    if (prop_gl)
    {
      *prop_gl = prop;
    }
    expr = dump_prop_getter_res (expr, prop);
  }

  lexer_save_token (tok);
  return expr;
}

/* call_expression
  : member_expression LT!* arguments (LT!* call_expression_suffix)*
  ;

   call_expression_suffix
  : arguments
  | index_suffix
  | property_reference_suffix
  ;

   arguments
  : '(' LT!* assignment_expression LT!* (',' LT!* assignment_expression * LT!*)* ')'
  ; */
static operand
parse_call_expression (operand *this_arg_gl, operand *prop_gl)
{
  operand this_arg = empty_operand ();
  operand expr = parse_member_expression (&this_arg, prop_gl);
  operand prop;

  skip_newlines ();
  if (!token_is (TOK_OPEN_PAREN))
  {
    lexer_save_token (tok);
    if (this_arg_gl != NULL)
    {
      *this_arg_gl = this_arg;
    }
    return expr;
  }

  expr = parse_argument_list (VARG_CALL_EXPR, expr, NULL, &this_arg);
  this_arg = empty_operand ();

  skip_newlines ();
  while (token_is (TOK_OPEN_PAREN) || token_is (TOK_OPEN_SQUARE)
         || token_is (TOK_DOT))
  {
    if (tok.type == TOK_OPEN_PAREN)
    {
      expr = parse_argument_list (VARG_CALL_EXPR, expr, NULL, &this_arg);
      skip_newlines ();
    }
    else
    {
      this_arg = expr;
      if (tok.type == TOK_OPEN_SQUARE)
      {
        skip_newlines ();
        prop = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
        next_token_must_be (TOK_CLOSE_SQUARE);
      }
      else if (tok.type == TOK_DOT)
      {
        token_after_newlines_must_be (TOK_NAME);
        prop = dump_string_assignment_res (token_data_as_lit_cp ());
      }
      expr = dump_prop_getter_res (expr, prop);
      skip_newlines ();
    }
  }
  lexer_save_token (tok);
  if (this_arg_gl != NULL)
  {
    *this_arg_gl = this_arg;
  }
  if (prop_gl != NULL)
  {
    *prop_gl = prop;
  }
  return expr;
}

/* left_hand_side_expression
  : call_expression
  | new_expression
  ; */
static operand
parse_left_hand_side_expression (operand *this_arg, operand *prop)
{
  return parse_call_expression (this_arg, prop);
}

/* postfix_expression
  : left_hand_side_expression ('++' | '--')?
  ; */
static operand
parse_postfix_expression (void)
{
  operand this_arg = empty_operand (), prop = empty_operand ();
  operand expr = parse_left_hand_side_expression (&this_arg, &prop);

  if (lexer_prev_token ().type == TOK_NEWLINE)
  {
    return expr;
  }

  syntax_check_for_eval_and_arguments_in_strict_mode (expr, is_strict_mode (), tok.loc);

  skip_token ();
  if (token_is (TOK_DOUBLE_PLUS))
  {
    const operand res = dump_post_increment_res (expr);
    if (!operand_is_empty (this_arg) && !operand_is_empty (prop))
    {
      dump_prop_setter (this_arg, prop, expr);
    }
    expr = res;
  }
  else if (token_is (TOK_DOUBLE_MINUS))
  {
    const operand res = dump_post_decrement_res (expr);
    if (!operand_is_empty (this_arg) && !operand_is_empty (prop))
    {
      dump_prop_setter (this_arg, prop, expr);
    }
    expr = res;
  }
  else
  {
    lexer_save_token (tok);
  }

  return expr;
}

/* unary_expression
  : postfix_expression
  | ('delete' | 'void' | 'typeof' | '++' | '--' | '+' | '-' | '~' | '!') unary_expression
  ; */
static operand
parse_unary_expression (operand *this_arg_gl, operand *prop_gl)
{
  operand expr, this_arg = empty_operand (), prop = empty_operand ();
  switch (tok.type)
  {
    case TOK_DOUBLE_PLUS:
    {
      skip_newlines ();
      expr = parse_unary_expression (&this_arg, &prop);
      syntax_check_for_eval_and_arguments_in_strict_mode (expr, is_strict_mode (), tok.loc);
      expr = dump_pre_increment_res (expr);
      if (!operand_is_empty (this_arg) && !operand_is_empty (prop))
      {
        dump_prop_setter (this_arg, prop, expr);
      }
      break;
    }
    case TOK_DOUBLE_MINUS:
    {
      skip_newlines ();
      expr = parse_unary_expression (&this_arg, &prop);
      syntax_check_for_eval_and_arguments_in_strict_mode (expr, is_strict_mode (), tok.loc);
      expr = dump_pre_decrement_res (expr);
      if (!operand_is_empty (this_arg) && !operand_is_empty (prop))
      {
        dump_prop_setter (this_arg, prop, expr);
      }
      break;
    }
    case TOK_PLUS:
    {
      skip_newlines ();
      expr = parse_unary_expression (NULL, NULL);
      expr = dump_unary_plus_res (expr);
      break;
    }
    case TOK_MINUS:
    {
      skip_newlines ();
      expr = parse_unary_expression (NULL, NULL);
      expr = dump_unary_minus_res (expr);
      break;
    }
    case TOK_COMPL:
    {
      skip_newlines ();
      expr = parse_unary_expression (NULL, NULL);
      expr = dump_bitwise_not_res (expr);
      break;
    }
    case TOK_NOT:
    {
      skip_newlines ();
      expr = parse_unary_expression (NULL, NULL);
      expr = dump_logical_not_res (expr);
      break;
    }
    case TOK_KEYWORD:
    {
      if (is_keyword (KW_DELETE))
      {
        skip_newlines ();
        expr = parse_unary_expression (NULL, NULL);
        expr = dump_delete_res (expr, is_strict_mode (), tok.loc);
        break;
      }
      else if (is_keyword (KW_VOID))
      {
        skip_newlines ();
        expr = parse_unary_expression (NULL, NULL);
        expr = dump_variable_assignment_res (expr);
        dump_undefined_assignment (expr);
        break;
      }
      else if (is_keyword (KW_TYPEOF))
      {
        skip_newlines ();
        expr = parse_unary_expression (NULL, NULL);
        expr = dump_typeof_res (expr);
        break;
      }
      /* FALLTHRU.  */
    }
    default:
    {
      expr = parse_postfix_expression ();
    }
  }

  if (this_arg_gl != NULL)
  {
    *this_arg_gl = this_arg;
  }
  if (prop_gl != NULL)
  {
    *prop_gl = prop;
  }

  return expr;
}

static operand
dump_assignment_of_lhs_if_literal (operand lhs)
{
  if (lhs.type == OPERAND_LITERAL)
  {
    lhs = dump_variable_assignment_res (lhs);
  }
  return lhs;
}

/* multiplicative_expression
  : unary_expression (LT!* ('*' | '/' | '%') LT!* unary_expression)*
  ; */
static operand
parse_multiplicative_expression (void)
{
  operand expr = parse_unary_expression (NULL, NULL);

  skip_newlines ();
  while (true)
  {
    switch (tok.type)
    {
      case TOK_MULT:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_multiplication_res (expr, parse_unary_expression (NULL, NULL));
        break;
      }
      case TOK_DIV:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_division_res (expr, parse_unary_expression (NULL, NULL));
        break;
      }
      case TOK_MOD:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_remainder_res (expr, parse_unary_expression (NULL, NULL));
        break;
      }
      default:
      {
        lexer_save_token (tok);
        goto done;
      }
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* additive_expression
  : multiplicative_expression (LT!* ('+' | '-') LT!* multiplicative_expression)*
  ; */
static operand
parse_additive_expression (void)
{
  operand expr = parse_multiplicative_expression ();

  skip_newlines ();
  while (true)
  {
    switch (tok.type)
    {
      case TOK_PLUS:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_addition_res (expr, parse_multiplicative_expression ());
        break;
      }
      case TOK_MINUS:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_substraction_res (expr, parse_multiplicative_expression ());
        break;
      }
      default:
      {
        lexer_save_token (tok);
        goto done;
      }
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* shift_expression
  : additive_expression (LT!* ('<<' | '>>' | '>>>') LT!* additive_expression)*
  ; */
static operand
parse_shift_expression (void)
{
  operand expr = parse_additive_expression ();

  skip_newlines ();
  while (true)
  {
    switch (tok.type)
    {
      case TOK_LSHIFT:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_left_shift_res (expr, parse_additive_expression ());
        break;
      }
      case TOK_RSHIFT:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_right_shift_res (expr, parse_additive_expression ());
        break;
      }
      case TOK_RSHIFT_EX:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_right_shift_ex_res (expr, parse_additive_expression ());
        break;
      }
      default:
      {
        lexer_save_token (tok);
        goto done;
      }
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* relational_expression
  : shift_expression (LT!* ('<' | '>' | '<=' | '>=' | 'instanceof' | 'in') LT!* shift_expression)*
  ; */
static operand
parse_relational_expression (bool in_allowed)
{
  operand expr = parse_shift_expression ();

  skip_newlines ();
  while (true)
  {
    switch (tok.type)
    {
      case TOK_LESS:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_less_than_res (expr, parse_shift_expression ());
        break;
      }
      case TOK_GREATER:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_greater_than_res (expr, parse_shift_expression ());
        break;
      }
      case TOK_LESS_EQ:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_less_or_equal_than_res (expr, parse_shift_expression ());
        break;
      }
      case TOK_GREATER_EQ:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_greater_or_equal_than_res (expr, parse_shift_expression ());
        break;
      }
      case TOK_KEYWORD:
      {
        if (is_keyword (KW_INSTANCEOF))
        {
          expr = dump_assignment_of_lhs_if_literal (expr);
          skip_newlines ();
          expr = dump_instanceof_res (expr, parse_shift_expression ());
          break;
        }
        else if (is_keyword (KW_IN))
        {
          if (in_allowed)
          {
            expr = dump_assignment_of_lhs_if_literal (expr);
            skip_newlines ();
            expr = dump_in_res (expr, parse_shift_expression ());
            break;
          }
        }
        /* FALLTHROUGH */
      }
      default:
      {
        lexer_save_token (tok);
        goto done;
      }
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* equality_expression
  : relational_expression (LT!* ('==' | '!=' | '===' | '!==') LT!* relational_expression)*
  ; */
static operand
parse_equality_expression (bool in_allowed)
{
  operand expr = parse_relational_expression (in_allowed);

  skip_newlines ();
  while (true)
  {
    switch (tok.type)
    {
      case TOK_DOUBLE_EQ:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_equal_value_res (expr, parse_relational_expression (in_allowed));
        break;
      }
      case TOK_NOT_EQ:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_not_equal_value_res (expr, parse_relational_expression (in_allowed));
        break;
      }
      case TOK_TRIPLE_EQ:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_equal_value_type_res (expr, parse_relational_expression (in_allowed));
        break;
      }
      case TOK_NOT_DOUBLE_EQ:
      {
        expr = dump_assignment_of_lhs_if_literal (expr);
        skip_newlines ();
        expr = dump_not_equal_value_type_res (expr, parse_relational_expression (in_allowed));
        break;
      }
      default:
      {
        lexer_save_token (tok);
        goto done;
      }
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* bitwise_and_expression
  : equality_expression (LT!* '&' LT!* equality_expression)*
  ; */
static operand
parse_bitwise_and_expression (bool in_allowed)
{
  operand expr = parse_equality_expression (in_allowed);
  skip_newlines ();
  while (true)
  {
    if (tok.type == TOK_AND)
    {
      expr = dump_assignment_of_lhs_if_literal (expr);
      skip_newlines ();
      expr = dump_bitwise_and_res (expr, parse_equality_expression (in_allowed));
    }
    else
    {
      lexer_save_token (tok);
      goto done;
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* bitwise_xor_expression
  : bitwise_and_expression (LT!* '^' LT!* bitwise_and_expression)*
  ; */
static operand
parse_bitwise_xor_expression (bool in_allowed)
{
  operand expr = parse_bitwise_and_expression (in_allowed);
  skip_newlines ();
  while (true)
  {
    if (tok.type == TOK_XOR)
    {
      expr = dump_assignment_of_lhs_if_literal (expr);
      skip_newlines ();
      expr = dump_bitwise_xor_res (expr, parse_bitwise_and_expression (in_allowed));
    }
    else
    {
      lexer_save_token (tok);
      goto done;
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* bitwise_or_expression
  : bitwise_xor_expression (LT!* '|' LT!* bitwise_xor_expression)*
  ; */
static operand
parse_bitwise_or_expression (bool in_allowed)
{
  operand expr = parse_bitwise_xor_expression (in_allowed);
  skip_newlines ();
  while (true)
  {
    if (tok.type == TOK_OR)
    {
      expr = dump_assignment_of_lhs_if_literal (expr);
      skip_newlines ();
      expr = dump_bitwise_or_res (expr, parse_bitwise_xor_expression (in_allowed));
    }
    else
    {
      lexer_save_token (tok);
      goto done;
    }
    skip_newlines ();
  }
done:
  return expr;
}

/* logical_and_expression
  : bitwise_or_expression (LT!* '&&' LT!* bitwise_or_expression)*
  ; */
static operand
parse_logical_and_expression (bool in_allowed)
{
  operand expr = parse_bitwise_or_expression (in_allowed), tmp;
  skip_newlines ();
  if (token_is (TOK_DOUBLE_AND))
  {
    tmp = dump_variable_assignment_res (expr);
    start_dumping_logical_and_checks ();
    dump_logical_and_check_for_rewrite (tmp);
  }
  else
  {
    lexer_save_token (tok);
    return expr;
  }
  while (token_is (TOK_DOUBLE_AND))
  {
    skip_newlines ();
    expr = parse_bitwise_or_expression (in_allowed);
    dump_variable_assignment (tmp, expr);
    skip_newlines ();
    if (token_is (TOK_DOUBLE_AND))
    {
      dump_logical_and_check_for_rewrite (tmp);
    }
  }
  lexer_save_token (tok);
  rewrite_logical_and_checks ();
  return tmp;
}

/* logical_or_expression
  : logical_and_expression (LT!* '||' LT!* logical_and_expression)*
  ; */
static operand
parse_logical_or_expression (bool in_allowed)
{
  operand expr = parse_logical_and_expression (in_allowed), tmp;
  skip_newlines ();
  if (token_is (TOK_DOUBLE_OR))
  {
    tmp = dump_variable_assignment_res (expr);
    start_dumping_logical_or_checks ();
    dump_logical_or_check_for_rewrite (tmp);
  }
  else
  {
    lexer_save_token (tok);
    return expr;
  }
  while (token_is (TOK_DOUBLE_OR))
  {
    skip_newlines ();
    expr = parse_logical_and_expression (in_allowed);
    dump_variable_assignment (tmp, expr);
    skip_newlines ();
    if (token_is (TOK_DOUBLE_OR))
    {
      dump_logical_or_check_for_rewrite (tmp);
    }
  }
  lexer_save_token (tok);
  rewrite_logical_or_checks ();
  return tmp;
}

/* conditional_expression
  : logical_or_expression (LT!* '?' LT!* assignment_expression LT!* ':' LT!* assignment_expression)?
  ; */
static operand
parse_conditional_expression (bool in_allowed, bool *is_conditional)
{
  operand expr = parse_logical_or_expression (in_allowed);
  skip_newlines ();
  if (token_is (TOK_QUERY))
  {
    dump_conditional_check_for_rewrite (expr);
    skip_newlines ();
    expr = parse_assignment_expression (in_allowed);
    operand tmp = dump_variable_assignment_res (expr);
    token_after_newlines_must_be (TOK_COLON);
    dump_jump_to_end_for_rewrite ();
    rewrite_conditional_check ();
    skip_newlines ();
    expr = parse_assignment_expression (in_allowed);
    dump_variable_assignment (tmp, expr);
    rewrite_jump_to_end ();
    if (is_conditional != NULL)
    {
      *is_conditional = true;
    }
    return tmp;
  }
  else
  {
    lexer_save_token (tok);
    return expr;
  }
}

/* assignment_expression
  : conditional_expression
  | left_hand_side_expression LT!* assignment_operator LT!* assignment_expression
  ; */
static operand
parse_assignment_expression (bool in_allowed)
{
  bool is_conditional = false;
  operand expr = parse_conditional_expression (in_allowed, &is_conditional);
  if (is_conditional)
  {
    return expr;
  }
  syntax_check_for_eval_and_arguments_in_strict_mode (expr, is_strict_mode (), tok.loc);
  skip_newlines ();
  switch (tok.type)
  {
    case TOK_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_variable_assignment_res (expr, assign_expr);
      break;
    }
    case TOK_MULT_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_multiplication_res (expr, assign_expr);
      break;
    }
    case TOK_DIV_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_division_res (expr, assign_expr);
      break;
    }
    case TOK_MOD_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_remainder_res (expr, assign_expr);
      break;
    }
    case TOK_PLUS_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_addition_res (expr, assign_expr);
      break;
    }
    case TOK_MINUS_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_substraction_res (expr, assign_expr);
      break;
    }
    case TOK_LSHIFT_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_left_shift_res (expr, assign_expr);
      break;
    }
    case TOK_RSHIFT_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_right_shift_res (expr, assign_expr);
      break;
    }
    case TOK_RSHIFT_EX_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_right_shift_ex_res (expr, assign_expr);
      break;
    }
    case TOK_AND_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_bitwise_and_res (expr, assign_expr);
      break;
    }
    case TOK_XOR_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_bitwise_xor_res (expr, assign_expr);
      break;
    }
    case TOK_OR_EQ:
    {
      skip_newlines ();
      start_dumping_assignment_expression ();
      const operand assign_expr = parse_assignment_expression (in_allowed);
      expr = dump_prop_setter_or_bitwise_or_res (expr, assign_expr);
      break;
    }
    default:
    {
      lexer_save_token (tok);
      break;
    }
  }
  return expr;
}

/**
 * Parse an expression
 *
 * expression
 *  : assignment_expression (LT!* ',' LT!* assignment_expression)*
 *  ;
 *
 * @return operand which holds result of expression
 */
static operand
parse_expression (bool in_allowed, /**< flag indicating if 'in' is allowed inside expression to parse */
                  jsp_eval_ret_store_t dump_eval_ret_store) /**< flag indicating if 'eval'
                                                             *   result code store should be dumped */
{
  operand expr = parse_assignment_expression (in_allowed);

  while (true)
  {
    skip_newlines ();
    if (token_is (TOK_COMMA))
    {
      skip_newlines ();
      expr = parse_assignment_expression (in_allowed);
    }
    else
    {
      lexer_save_token (tok);
      break;
    }
  }

  if (inside_eval
      && dump_eval_ret_store == JSP_EVAL_RET_STORE_DUMP
      && !inside_function)
  {
    dump_variable_assignment (eval_ret_operand () , expr);
  }

  return expr;
} /* parse_expression */

/* variable_declaration
  : Identifier LT!* initialiser?
  ;
   initialiser
  : '=' LT!* assignment_expression
  ; */
static void
parse_variable_declaration (void)
{
  current_token_must_be (TOK_NAME);
  const operand name = literal_operand (token_data_as_lit_cp ());

  skip_newlines ();
  if (token_is (TOK_EQ))
  {
    skip_newlines ();
    const operand expr = parse_assignment_expression (true);
    dump_variable_assignment (name, expr);
  }
  else
  {
    lexer_save_token (tok);
  }
}

/* variable_declaration_list
  : variable_declaration
    (LT!* ',' LT!* variable_declaration)*
  ; */
static void
parse_variable_declaration_list (bool *several_decls)
{
  while (true)
  {
    parse_variable_declaration ();

    skip_newlines ();
    if (!token_is (TOK_COMMA))
    {
      lexer_save_token (tok);
      return;
    }

    skip_newlines ();
    if (several_decls)
    {
      *several_decls = true;
    }
  }
}

static void
parse_plain_for (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to
                                                       *   the statement (or NULL, if there are no named
                                                       *   labels associated with the statement) */
{
  dump_jump_to_end_for_rewrite ();

  // Skip till body
  JERRY_ASSERT (token_is (TOK_SEMICOLON));
  skip_newlines ();
  const locus cond_loc = tok.loc;
  while (!token_is (TOK_SEMICOLON))
  {
    skip_newlines ();
  }
  skip_newlines ();
  const locus incr_loc = tok.loc;
  while (!token_is (TOK_CLOSE_PAREN))
  {
    skip_newlines ();
  }

  dumper_set_next_interation_target ();

  // Parse body
  skip_newlines ();
  parse_statement (NULL);

  const locus end_loc = tok.loc;

  jsp_label_setup_continue_target (outermost_stmt_label_p,
                                   serializer_get_current_opcode_counter ());

  lexer_seek (incr_loc);
  skip_token ();
  if (!token_is (TOK_CLOSE_PAREN))
  {
    parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
  }

  rewrite_jump_to_end ();

  lexer_seek (cond_loc);
  skip_token ();
  if (token_is (TOK_SEMICOLON))
  {
    dump_continue_iterations_check (empty_operand ());
  }
  else
  {
    const operand cond = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
    dump_continue_iterations_check (cond);
  }

  lexer_seek (end_loc);
  skip_token ();
  if (tok.type != TOK_CLOSE_BRACE)
  {
    lexer_save_token (tok);
  }
}

static void
parse_for_in (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to
                                                    *   the statement (or NULL, if there are no named
                                                    *   labels associated with the statement) */
{
  (void) outermost_stmt_label_p;

  EMIT_SORRY ("'for in' loops are not supported yet");
}

/* for_statement
  : 'for' LT!* '(' (LT!* for_statement_initialiser_part)? LT!* ';'
    (LT!* expression)? LT!* ';' (LT!* expression)? LT!* ')' LT!* statement
  ;

   for_statement_initialiser_part
  : expression
  | 'var' LT!* variable_declaration_list
  ;

   for_in_statement
  : 'for' LT!* '(' LT!* for_in_statement_initialiser_part LT!* 'in'
    LT!* expression LT!* ')' LT!* statement
  ;

   for_in_statement_initialiser_part
  : left_hand_side_expression
  | 'var' LT!* variable_declaration
  ;*/

static void
parse_for_or_for_in_statement (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to
                                                                     *   the statement (or NULL, if there are no named
                                                                     *   labels associated with the statement) */
{
  assert_keyword (KW_FOR);
  token_after_newlines_must_be (TOK_OPEN_PAREN);

  skip_newlines ();
  if (token_is (TOK_SEMICOLON))
  {
    parse_plain_for (outermost_stmt_label_p);
    return;
  }
  /* Both for_statement_initialiser_part and for_in_statement_initialiser_part
     contains 'var'. Check it first.  */
  if (is_keyword (KW_VAR))
  {
    bool several_decls = false;
    skip_newlines ();
    parse_variable_declaration_list (&several_decls);
    if (several_decls)
    {
      token_after_newlines_must_be (TOK_SEMICOLON);
      parse_plain_for (outermost_stmt_label_p);
      return;
    }
    else
    {
      skip_newlines ();
      if (token_is (TOK_SEMICOLON))
      {
        parse_plain_for (outermost_stmt_label_p);
        return;
      }
      else if (is_keyword (KW_IN))
      {
        parse_for_in (outermost_stmt_label_p);
        return;
      }
      else
      {
        EMIT_ERROR ("Expected either ';' or 'in' token");
      }
    }
  }

  /* expression contains left_hand_side_expression.  */
  parse_expression (false, JSP_EVAL_RET_STORE_NOT_DUMP);

  skip_newlines ();
  if (token_is (TOK_SEMICOLON))
  {
    parse_plain_for (outermost_stmt_label_p);
    return;
  }
  else if (is_keyword (KW_IN))
  {
    parse_for_in (outermost_stmt_label_p);
    return;
  }
  else
  {
    EMIT_ERROR ("Expected either ';' or 'in' token");
  }
}

static operand
parse_expression_inside_parens (void)
{
  token_after_newlines_must_be (TOK_OPEN_PAREN);
  skip_newlines ();
  const operand res = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
  token_after_newlines_must_be (TOK_CLOSE_PAREN);
  return res;
}

/* statement_list
  : statement (LT!* statement)*
  ; */
static void
parse_statement_list (void)
{
  while (true)
  {
    parse_statement (NULL);

    skip_newlines ();
    while (token_is (TOK_SEMICOLON))
    {
      skip_newlines ();
    }
    if (token_is (TOK_CLOSE_BRACE))
    {
      lexer_save_token (tok);
      break;
    }
    if (is_keyword (KW_CASE) || is_keyword (KW_DEFAULT))
    {
      lexer_save_token (tok);
      break;
    }
  }
}

/* if_statement
  : 'if' LT!* '(' LT!* expression LT!* ')' LT!* statement (LT!* 'else' LT!* statement)?
  ; */
static void
parse_if_statement (void)
{
  assert_keyword (KW_IF);

  const operand cond = parse_expression_inside_parens ();
  dump_conditional_check_for_rewrite (cond);

  skip_newlines ();
  parse_statement (NULL);

  skip_newlines ();
  if (is_keyword (KW_ELSE))
  {
    dump_jump_to_end_for_rewrite ();
    rewrite_conditional_check ();

    skip_newlines ();
    parse_statement (NULL);

    rewrite_jump_to_end ();
  }
  else
  {
    lexer_save_token (tok);
    rewrite_conditional_check ();
  }
}

/* do_while_statement
  : 'do' LT!* statement LT!* 'while' LT!* '(' expression ')' (LT | ';')!
  ; */
static void
parse_do_while_statement (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to
                                                                *   the statement (or NULL, if there are no named
                                                                *   labels associated with the statement) */
{
  assert_keyword (KW_DO);

  dumper_set_next_interation_target ();

  skip_newlines ();
  parse_statement (NULL);

  jsp_label_setup_continue_target (outermost_stmt_label_p,
                                   serializer_get_current_opcode_counter ());

  token_after_newlines_must_be_keyword (KW_WHILE);
  const operand cond = parse_expression_inside_parens ();
  dump_continue_iterations_check (cond);
}

/* while_statement
  : 'while' LT!* '(' LT!* expression LT!* ')' LT!* statement
  ; */
static void
parse_while_statement (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to
                                                             *   the statement (or NULL, if there are no named
                                                             *   labels associated with the statement) */
{
  assert_keyword (KW_WHILE);

  token_after_newlines_must_be (TOK_OPEN_PAREN);
  const locus cond_loc = tok.loc;
  skip_parens ();

  dump_jump_to_end_for_rewrite ();

  dumper_set_next_interation_target ();

  skip_newlines ();
  parse_statement (NULL);

  jsp_label_setup_continue_target (outermost_stmt_label_p,
                                   serializer_get_current_opcode_counter ());

  rewrite_jump_to_end ();

  const locus end_loc = tok.loc;
  lexer_seek (cond_loc);
  const operand cond = parse_expression_inside_parens ();
  dump_continue_iterations_check (cond);

  lexer_seek (end_loc);
  skip_token ();
}

/* with_statement
  : 'with' LT!* '(' LT!* expression LT!* ')' LT!* statement
  ; */
static void
parse_with_statement (void)
{
  assert_keyword (KW_WITH);
  if (is_strict_mode ())
  {
    EMIT_ERROR ("'with' expression is not allowed in strict mode.");
  }
  const operand expr = parse_expression_inside_parens ();

  jsp_label_raise_nested_jumpable_border ();

  opcode_counter_t with_begin_oc = dump_with_for_rewrite (expr);
  skip_newlines ();
  parse_statement (NULL);
  rewrite_with (with_begin_oc);
  dump_with_end ();

  jsp_label_remove_nested_jumpable_border ();
}

static void
skip_case_clause_body (void)
{
  while (!is_keyword (KW_CASE)
         && !is_keyword (KW_DEFAULT)
         && !token_is (TOK_CLOSE_BRACE))
  {
    if (token_is (TOK_OPEN_BRACE))
    {
      skip_braces ();
    }
    skip_newlines ();
  }
}

/* switch_statement
  : 'switch' LT!* '(' LT!* expression LT!* ')' LT!* '{' LT!* case_block LT!* '}'
  ;
   case_block
  : '{' LT!* case_clause* LT!* '}'
  | '{' LT!* case_clause* LT!* default_clause LT!* case_clause* LT!* '}'
  ;
   case_clause
  : 'case' LT!* expression LT!* ':' LT!* statement*
  ; */
static void
parse_switch_statement (void)
{
  assert_keyword (KW_SWITCH);

  const operand switch_expr = parse_expression_inside_parens ();
  token_after_newlines_must_be (TOK_OPEN_BRACE);

  start_dumping_case_clauses ();
  const locus start_loc = tok.loc;
  bool was_default = false;
  // First, generate table of jumps
  skip_newlines ();
  while (is_keyword (KW_CASE) || is_keyword (KW_DEFAULT))
  {
    if (is_keyword (KW_CASE))
    {
      skip_newlines ();
      const operand case_expr = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
      next_token_must_be (TOK_COLON);
      dump_case_clause_check_for_rewrite (switch_expr, case_expr);
      skip_newlines ();
      skip_case_clause_body ();
    }
    else if (is_keyword (KW_DEFAULT))
    {
      if (was_default)
      {
        EMIT_ERROR ("Duplication of 'default' clause");
      }
      was_default = true;
      token_after_newlines_must_be (TOK_COLON);
      skip_newlines ();
      skip_case_clause_body ();
    }
  }
  current_token_must_be (TOK_CLOSE_BRACE);

  if (was_default)
  {
    dump_default_clause_check_for_rewrite ();
  }

  lexer_seek (start_loc);
  next_token_must_be (TOK_OPEN_BRACE);

  jsp_label_t label;
  jsp_label_push (&label,
                  JSP_LABEL_TYPE_UNNAMED_BREAKS,
                  TOKEN_EMPTY_INITIALIZER);

  // Second, parse case clauses' bodies and rewrite jumps
  skip_newlines ();
  while (is_keyword (KW_CASE) || is_keyword (KW_DEFAULT))
  {
    if (is_keyword (KW_CASE))
    {
      while (!token_is (TOK_COLON))
      {
        skip_newlines ();
      }
      rewrite_case_clause ();
      skip_newlines ();
      if (is_keyword (KW_CASE) || is_keyword (KW_DEFAULT))
      {
        continue;
      }
      parse_statement_list ();
    }
    else if (is_keyword (KW_DEFAULT))
    {
      token_after_newlines_must_be (TOK_COLON);
      skip_newlines ();
      rewrite_default_clause ();
      if (is_keyword (KW_CASE))
      {
        continue;
      }
      parse_statement_list ();
      continue;
    }
    skip_newlines ();
  }
  current_token_must_be (TOK_CLOSE_BRACE);
  skip_token ();

  jsp_label_rewrite_jumps_and_pop (&label,
                                   serializer_get_current_opcode_counter ());

  finish_dumping_case_clauses ();
}

/* catch_clause
  : 'catch' LT!* '(' LT!* Identifier LT!* ')' LT!* '{' LT!* statement_list LT!* '}'
  ; */
static void
parse_catch_clause (void)
{
  assert_keyword (KW_CATCH);

  token_after_newlines_must_be (TOK_OPEN_PAREN);
  token_after_newlines_must_be (TOK_NAME);
  const operand exception = literal_operand (token_data_as_lit_cp ());
  syntax_check_for_eval_and_arguments_in_strict_mode (exception, is_strict_mode (), tok.loc);
  token_after_newlines_must_be (TOK_CLOSE_PAREN);

  dump_catch_for_rewrite (exception);

  token_after_newlines_must_be (TOK_OPEN_BRACE);
  skip_newlines ();
  parse_statement_list ();
  next_token_must_be (TOK_CLOSE_BRACE);

  rewrite_catch ();
}

/* finally_clause
  : 'finally' LT!* '{' LT!* statement_list LT!* '}'
  ; */
static void
parse_finally_clause (void)
{
  assert_keyword (KW_FINALLY);

  dump_finally_for_rewrite ();

  token_after_newlines_must_be (TOK_OPEN_BRACE);
  skip_newlines ();
  parse_statement_list ();
  next_token_must_be (TOK_CLOSE_BRACE);

  rewrite_finally ();
}

/* try_statement
  : 'try' LT!* '{' LT!* statement_list LT!* '}' LT!* (finally_clause | catch_clause (LT!* finally_clause)?)
  ; */
static void
parse_try_statement (void)
{
  assert_keyword (KW_TRY);

  jsp_label_raise_nested_jumpable_border ();

  dump_try_for_rewrite ();

  token_after_newlines_must_be (TOK_OPEN_BRACE);
  skip_newlines ();
  parse_statement_list ();
  next_token_must_be (TOK_CLOSE_BRACE);

  rewrite_try ();

  token_after_newlines_must_be (TOK_KEYWORD);
  if (is_keyword (KW_CATCH))
  {
    parse_catch_clause ();

    skip_newlines ();
    if (is_keyword (KW_FINALLY))
    {
      parse_finally_clause ();
    }
    else
    {
      lexer_save_token (tok);
    }
  }
  else if (is_keyword (KW_FINALLY))
  {
    parse_finally_clause ();
  }
  else
  {
    EMIT_ERROR ("Expected either 'catch' or 'finally' token");
  }

  dump_end_try_catch_finally ();

  jsp_label_remove_nested_jumpable_border ();
}

static void
insert_semicolon (void)
{
  // We cannot use tok, since we may use lexer_save_token
  skip_token ();
  if (token_is (TOK_NEWLINE) || lexer_prev_token ().type == TOK_NEWLINE)
  {
    lexer_save_token (tok);
    return;
  }
  if (token_is (TOK_CLOSE_BRACE))
  {
    lexer_save_token (tok);
    return;
  }
  else if (!token_is (TOK_SEMICOLON))
  {
    EMIT_ERROR ("Expected either ';' or newline token");
  }
}

/**
 * iteration_statement
 *  : do_while_statement
 *  | while_statement
 *  | for_statement
 *  | for_in_statement
 *  ;
 */
static void
parse_iterational_statement (jsp_label_t *outermost_named_stmt_label_p) /**< outermost (first) named label,
                                                                         *   corresponding to the statement
                                                                         *   (or NULL, if there are no named
                                                                         *    labels associated with the statement) */
{
  jsp_label_t label;
  jsp_label_push (&label,
                  (jsp_label_type_flag_t) (JSP_LABEL_TYPE_UNNAMED_BREAKS | JSP_LABEL_TYPE_UNNAMED_CONTINUES),
                  TOKEN_EMPTY_INITIALIZER);

  jsp_label_t *outermost_stmt_label_p = (outermost_named_stmt_label_p != NULL ? outermost_named_stmt_label_p : &label);

  if (is_keyword (KW_DO))
  {
    parse_do_while_statement (outermost_stmt_label_p);
  }
  else if (is_keyword (KW_WHILE))
  {
    parse_while_statement (outermost_stmt_label_p);
  }
  else
  {
    JERRY_ASSERT (is_keyword (KW_FOR));
    parse_for_or_for_in_statement (outermost_stmt_label_p);
  }

  jsp_label_rewrite_jumps_and_pop (&label,
                                   serializer_get_current_opcode_counter ());
} /* parse_iterational_statement */

/* statement
  : statement_block
  | variable_statement
  | empty_statement
  | if_statement
  | iteration_statement
  | continue_statement
  | break_statement
  | return_statement
  | with_statement
  | labelled_statement
  | switch_statement
  | throw_statement
  | try_statement
  | expression_statement
  ;

   statement_block
  : '{' LT!* statement_list? LT!* '}'
  ;

   variable_statement
  : 'var' LT!* variable_declaration_list (LT | ';')!
  ;

   empty_statement
  : ';'
  ;

   expression_statement
  : expression (LT | ';')!
  ;

   iteration_statement
  : do_while_statement
  | while_statement
  | for_statement
  | for_in_statement
  ;

   continue_statement
  : 'continue' Identifier? (LT | ';')!
  ;

   break_statement
  : 'break' Identifier? (LT | ';')!
  ;

   return_statement
  : 'return' expression? (LT | ';')!
  ;

   switchStatement
  : 'switch' LT!* '(' LT!* expression LT!* ')' LT!* caseBlock
  ;

   throw_statement
  : 'throw' expression (LT | ';')!
  ;

   try_statement
  : 'try' LT!* '{' LT!* statement_list LT!* '}' LT!* (finally_clause | catch_clause (LT!* finally_clause)?)
  ;*/
static void
parse_statement (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to the statement
                                                       *   (or NULL, if there are no named labels associated
                                                       *    with the statement) */
{
  dumper_new_statement ();

  if (token_is (TOK_CLOSE_BRACE))
  {
    lexer_save_token (tok);
    return;
  }
  if (token_is (TOK_OPEN_BRACE))
  {
    skip_newlines ();
    if (!token_is (TOK_CLOSE_BRACE))
    {
      parse_statement_list ();
      next_token_must_be (TOK_CLOSE_BRACE);
    }
    return;
  }
  if (is_keyword (KW_VAR))
  {
    skip_newlines ();
    parse_variable_declaration_list (NULL);
    return;
  }
  if (is_keyword (KW_FUNCTION))
  {
    parse_function_declaration ();
    return;
  }
  if (token_is (TOK_SEMICOLON))
  {
    return;
  }
  if (is_keyword (KW_CASE) || is_keyword (KW_DEFAULT))
  {
    return;
  }
  if (is_keyword (KW_IF))
  {
    parse_if_statement ();
    return;
  }
  if (is_keyword (KW_DO)
      || is_keyword (KW_WHILE)
      || is_keyword (KW_FOR))
  {
    parse_iterational_statement (outermost_stmt_label_p);
    return;
  }
  if (is_keyword (KW_CONTINUE)
      || is_keyword (KW_BREAK))
  {
    bool is_break = is_keyword (KW_BREAK);

    skip_token ();

    jsp_label_t *label_p;
    bool is_simply_jumpable = true;
    if (token_is (TOK_NAME))
    {
      /* break or continue on a label */
      label_p = jsp_label_find (JSP_LABEL_TYPE_NAMED, tok, &is_simply_jumpable);

      if (label_p == NULL)
      {
        EMIT_ERROR ("Label not found");
      }
    }
    else if (is_break)
    {
      label_p = jsp_label_find (JSP_LABEL_TYPE_UNNAMED_BREAKS,
                                TOKEN_EMPTY_INITIALIZER,
                                &is_simply_jumpable);

      if (label_p == NULL)
      {
        EMIT_ERROR ("No corresponding statement for the break");
      }
    }
    else
    {
      JERRY_ASSERT (!is_break);

      label_p = jsp_label_find (JSP_LABEL_TYPE_UNNAMED_CONTINUES,
                                TOKEN_EMPTY_INITIALIZER,
                                &is_simply_jumpable);

      if (label_p == NULL)
      {
        EMIT_ERROR ("No corresponding statement for the continue");
      }
    }

    JERRY_ASSERT (label_p != NULL);

    jsp_label_add_jump (label_p, is_simply_jumpable, is_break);

    return;
  }
  if (is_keyword (KW_RETURN))
  {
    skip_token ();
    if (!token_is (TOK_SEMICOLON) && !token_is (TOK_NEWLINE))
    {
      const operand op = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
      dump_retval (op);
      insert_semicolon ();
      return;
    }
    else
    {
      dump_ret ();
      return;
    }
  }
  if (is_keyword (KW_WITH))
  {
    parse_with_statement ();
    return;
  }
  if (is_keyword (KW_SWITCH))
  {
    parse_switch_statement ();
    return;
  }
  if (is_keyword (KW_THROW))
  {
    skip_token ();
    const operand op = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP);
    insert_semicolon ();
    dump_throw (op);
    return;
  }
  if (is_keyword (KW_TRY))
  {
    parse_try_statement ();
    return;
  }
  if (token_is (TOK_NAME))
  {
    const token temp = tok;
    skip_newlines ();
    if (token_is (TOK_COLON))
    {
      skip_newlines ();

      jsp_label_t *label_p = jsp_label_find (JSP_LABEL_TYPE_NAMED, temp, NULL);
      if (label_p != NULL)
      {
        EMIT_ERROR ("Label is duplicated");
      }

      jsp_label_t label;
      jsp_label_push (&label, JSP_LABEL_TYPE_NAMED, temp);

      parse_statement (outermost_stmt_label_p != NULL ? outermost_stmt_label_p : &label);

      jsp_label_rewrite_jumps_and_pop (&label,
                                       serializer_get_current_opcode_counter ());
    }
    else
    {
      lexer_save_token (tok);
      tok = temp;
      parse_expression (true, JSP_EVAL_RET_STORE_DUMP);
      skip_newlines ();
      if (!token_is (TOK_SEMICOLON))
      {
        lexer_save_token (tok);
      }
      return;
    }
  }
  else
  {
    parse_expression (true, JSP_EVAL_RET_STORE_DUMP);
    skip_newlines ();
    if (!token_is (TOK_SEMICOLON))
    {
      lexer_save_token (tok);
    }
    return;
  }
}

/* source_element
  : function_declaration
  | statement
  ; */
static void
parse_source_element (void)
{
  if (is_keyword (KW_FUNCTION))
  {
    parse_function_declaration ();
  }
  else
  {
    parse_statement (NULL);
  }
}

static void
skip_optional_name_and_parens (void)
{
  if (token_is (TOK_NAME))
  {
    token_after_newlines_must_be (TOK_OPEN_PAREN);
  }
  else
  {
    current_token_must_be (TOK_OPEN_PAREN);
  }

  while (!token_is (TOK_CLOSE_PAREN))
  {
    skip_newlines ();
  }
}

static void process_keyword_names ()
{
  if (token_is (TOK_KEYWORD))
  {
    keyword kw = (keyword) token_data ();
    skip_newlines ();
    if (token_is (TOK_COLON))
    {
      const char *s = lexer_keyword_to_string (kw);
      lit_find_or_create_literal_from_charset ((const ecma_char_t *) s, (ecma_length_t) strlen (s));
    }
    else
    {
      lexer_save_token (tok);
    }
  }
  else if (token_is (TOK_NAME))
  {
    if (lit_literal_equal_type_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()), (const ecma_char_t *) "get")
        || lit_literal_equal_type_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()), (const ecma_char_t *) "set"))
    {
      skip_newlines ();
      if (token_is (TOK_KEYWORD))
      {
        keyword kw = (keyword) token_data ();
        skip_newlines ();
        if (token_is (TOK_OPEN_PAREN))
        {
          const char *s = lexer_keyword_to_string (kw);
          lit_find_or_create_literal_from_charset ((const ecma_char_t *) s, (ecma_length_t) strlen (s));
        }
        else
        {
          lexer_save_token (tok);
        }
      }
      else
      {
        lexer_save_token (tok);
      }
    }
  }
}

static void
skip_braces (void)
{
  current_token_must_be (TOK_OPEN_BRACE);

  uint8_t nesting_level = 1;
  while (nesting_level > 0)
  {
    skip_newlines ();
    if (token_is (TOK_OPEN_BRACE))
    {
      nesting_level++;
    }
    else if (token_is (TOK_CLOSE_BRACE))
    {
      nesting_level--;
    }
    else
    {
      process_keyword_names ();
    }
  }
}

static void
skip_function (void)
{
  skip_newlines ();
  skip_optional_name_and_parens ();
  skip_newlines ();
  skip_braces ();
}

static void
skip_squares (void)
{
  current_token_must_be (TOK_OPEN_SQUARE);

  uint8_t nesting_level = 1;
  while (nesting_level > 0)
  {
    skip_newlines ();
    if (token_is (TOK_OPEN_SQUARE))
    {
      nesting_level++;
    }
    else if (token_is (TOK_CLOSE_SQUARE))
    {
      nesting_level--;
    }
  }
}

static void
skip_parens (void)
{
  current_token_must_be (TOK_OPEN_PAREN);

  uint8_t nesting_level = 1;
  while (nesting_level > 0)
  {
    skip_newlines ();
    if (token_is (TOK_OPEN_PAREN))
    {
      nesting_level++;
    }
    else if (token_is (TOK_CLOSE_PAREN))
    {
      nesting_level--;
    }
  }
}

static bool
var_declared (lit_cpointer_t var_cp)
{
  return dumper_variable_declaration_exists (var_cp);
}

static void
preparse_var_decls (void)
{
  assert_keyword (KW_VAR);

  skip_newlines ();
  while (!token_is (TOK_NEWLINE) && !token_is (TOK_SEMICOLON) && !is_keyword (KW_IN))
  {
    if (token_is (TOK_NAME))
    {
      if (!var_declared (token_data_as_lit_cp ()))
      {
        syntax_check_for_eval_and_arguments_in_strict_mode (literal_operand (token_data_as_lit_cp ()),
                                                            is_strict_mode (),
                                                            tok.loc);
        dump_variable_declaration (token_data_as_lit_cp ());
      }
      skip_token ();
      continue;
    }
    else if (token_is (TOK_EQ))
    {
      while (!token_is (TOK_COMMA) && !token_is (TOK_NEWLINE) && !token_is (TOK_SEMICOLON))
      {
        if (is_keyword (KW_FUNCTION))
        {
          skip_function ();
        }
        else if (token_is (TOK_OPEN_BRACE))
        {
          skip_braces ();
        }
        else if (token_is (TOK_OPEN_SQUARE))
        {
          skip_squares ();
        }
        else if (token_is (TOK_OPEN_PAREN))
        {
          skip_parens ();
        }
        skip_token ();
      }
    }
    else if (!token_is (TOK_COMMA))
    {
      EMIT_ERROR ("Expected ','");
    }
    else
    {
      skip_token ();
      continue;
    }
  }
}

static void
preparse_scope (bool is_global)
{
  const locus start_loc = tok.loc;
  const token_type end_tt = is_global ? TOK_EOF : TOK_CLOSE_BRACE;

  opcode_counter_t scope_code_flags_oc = dump_scope_code_flags_for_rewrite ();

  bool is_ref_arguments_identifier = false;
  bool is_ref_eval_identifier = false;
  bool is_use_strict = false;

  if (token_is (TOK_STRING) && lit_literal_equal_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()),
                                                     (const ecma_char_t *) "use strict"))
  {
    scopes_tree_set_strict_mode (STACK_TOP (scopes), true);
    is_use_strict = true;
  }

  lexer_set_strict_mode (scopes_tree_strict_mode (STACK_TOP (scopes)));

  dump_reg_var_decl_for_rewrite ();

  size_t nesting_level = 0;
  while (nesting_level > 0 || !token_is (end_tt))
  {
    if (token_is (TOK_OPEN_BRACE))
    {
      nesting_level++;
    }
    else if (token_is (TOK_CLOSE_BRACE))
    {
      nesting_level--;
    }
    else if (is_keyword (KW_VAR))
    {
      preparse_var_decls ();
    }
    else if (is_keyword (KW_FUNCTION))
    {
      skip_function ();
    }
    else if (token_is (TOK_OPEN_BRACE))
    {
      skip_braces ();
    }
    else
    {
      if (token_is (TOK_NAME))
      {
        if (lit_literal_equal_type_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()),
                                       (const ecma_char_t *) "arguments"))
        {
          is_ref_arguments_identifier = true;
        }
        else if (lit_literal_equal_type_zt (lit_get_literal_by_cp (token_data_as_lit_cp ()),
                                            (const ecma_char_t *) "eval"))
        {
          is_ref_eval_identifier = true;
        }
      }

      process_keyword_names ();
    }
    skip_newlines ();
  }

  opcode_scope_code_flags_t scope_flags = OPCODE_SCOPE_CODE_FLAGS__EMPTY;

  if (is_use_strict)
  {
    scope_flags = (opcode_scope_code_flags_t) (scope_flags | OPCODE_SCOPE_CODE_FLAGS_STRICT);
  }

  if (!is_ref_arguments_identifier)
  {
    scope_flags = (opcode_scope_code_flags_t) (scope_flags | OPCODE_SCOPE_CODE_FLAGS_NOT_REF_ARGUMENTS_IDENTIFIER);
  }

  if (!is_ref_eval_identifier)
  {
    scope_flags = (opcode_scope_code_flags_t) (scope_flags | OPCODE_SCOPE_CODE_FLAGS_NOT_REF_EVAL_IDENTIFIER);
  }

  rewrite_scope_code_flags (scope_code_flags_oc, scope_flags);

  if (start_loc != tok.loc)
  {
    lexer_seek (start_loc);
  }
  else
  {
    lexer_save_token (tok);
  }
}

/**
 * Parse source element list
 *
 *  source_element_list
 *   : source_element (LT!* source_element)*
 *   ;
 */
static void
parse_source_element_list (bool is_global) /**< flag indicating if we are parsing the global scope */
{
  dumper_new_scope ();
  preparse_scope (is_global);

  if (inside_eval
      && !inside_function)
  {
    dump_undefined_assignment (eval_ret_operand ());
  }

  skip_newlines ();
  while (!token_is (TOK_EOF) && !token_is (TOK_CLOSE_BRACE))
  {
    parse_source_element ();
    skip_newlines ();
  }
  lexer_save_token (tok);
  rewrite_reg_var_decl ();
  dumper_finish_scope ();
} /* parse_source_element_list */

/**
 * Parse program
 *
 * program
 *  : LT!* source_element_list LT!* EOF!
 *  ;
 *
 * @return true - if parse finished successfully (no SyntaxError was raised);
 *         false - otherwise.
 */
static bool
parser_parse_program (const char *source_p, /**< source code buffer */
                      size_t source_size, /**< source code size in bytes */
                      bool in_function, /**< flag indicating if we are parsing body of a function */
                      bool in_eval, /**< flag indicating if we are parsing body of eval code */
                      const opcode_t **out_opcodes_p) /**< out: generated byte-code array
                                                       *  (in case there were no syntax errors) */
{
  JERRY_ASSERT (out_opcodes_p != NULL);

  inside_function = in_function;
  inside_eval = in_eval;

#ifndef JERRY_NDEBUG
  volatile bool is_parse_finished = false;
#endif /* !JERRY_NDEBUG */

  bool is_syntax_correct;

  jsp_mm_init ();
  jsp_label_init ();

  lexer_init (source_p, source_size, parser_show_opcodes);

  serializer_set_show_opcodes (parser_show_opcodes);
  dumper_init ();
  syntax_init ();

  STACK_INIT (scopes);
  STACK_PUSH (scopes, scopes_tree_init (NULL));
  serializer_set_scope (STACK_TOP (scopes));
  lexer_set_strict_mode (scopes_tree_strict_mode (STACK_TOP (scopes)));

  jmp_buf *syntax_error_label_p = syntax_get_syntax_error_longjmp_label ();
  int r = setjmp (*syntax_error_label_p);

  if (r == 0)
  {
    skip_newlines ();
    parse_source_element_list (true);

    skip_newlines ();
    JERRY_ASSERT (token_is (TOK_EOF));

    if (in_function)
    {
      dump_ret ();
    }
    else if (inside_eval)
    {
      dump_retval (eval_ret_operand ());
    }
    else
    {
      dump_exit ();
    }

#ifndef JERRY_NDEBUG
    is_parse_finished = true;
#endif /* !JERRY_NDEBUG */

    syntax_free ();

    *out_opcodes_p = serializer_merge_scopes_into_bytecode ();

    dumper_free ();

    serializer_set_scope (NULL);
    scopes_tree_free (STACK_TOP (scopes));
    STACK_DROP (scopes, 1);
    STACK_FREE (scopes);

    is_syntax_correct = true;
  }
  else
  {
    /* SyntaxError handling */

#ifndef JERRY_NDEBUG
    JERRY_ASSERT (!is_parse_finished);
#endif /* !JERRY_NDEBUG */

    *out_opcodes_p = NULL;

    jsp_label_remove_all_labels ();
    jsp_mm_free_all ();

    is_syntax_correct = false;
  }

  jsp_label_finalize ();
  jsp_mm_finalize ();

  return is_syntax_correct;
} /* parser_parse_program */

/**
 * Parse source script
 *
 * @return true - if parse finished successfully (no SyntaxError were raised);
 *         false - otherwise.
 */
bool
parser_parse_script (const char *source, /**< source script */
                     size_t source_size, /**< source script size it bytes */
                     const opcode_t **opcodes_p) /**< out: generated byte-code array
                                                  *  (in case there were no syntax errors) */
{
  return parser_parse_program (source, source_size, false, false, opcodes_p);
} /* parser_parse_script */

/**
 * Parse string passed to eval() call
 *
 * @return true - if parse finished successfully (no SyntaxError were raised);
 *         false - otherwise.
 */
bool
parser_parse_eval (const char *source, /**< string passed to eval() */
                   size_t source_size, /**< string size in bytes */
                   const opcode_t **opcodes_p) /**< out: generated byte-code array
                                                *  (in case there were no syntax errors) */
{
  return parser_parse_program (source, source_size, false, true, opcodes_p);
} /* parser_parse_eval */

/**
 * Parse a function created via new Function call
 *
 * NOTE: Array of arguments should contain at least one element.
 *       In case of new Function() call without parameters, empty string should be passed as argument to this
 *       function.
 *
 * @return true - if parse finished successfully (no SyntaxError were raised);
 *         false - otherwise.
 */
bool
parser_parse_new_function (const char **params, /**< array of arguments of new Function (p1, p2, ..., pn, body) call */
                           size_t params_count, /**< total number of arguments passed to new Function (...) */
                           const opcode_t **out_opcodes_p) /**< out: generated byte-code array
                                                            *  (in case there were no syntax errors) */
{
  // Process arguments
  JERRY_ASSERT (params_count > 0);
  for (size_t i = 0; i < params_count - 1; ++i)
  {
    FIXME ("check parameter's name for syntax errors");
    lit_find_or_create_literal_from_charset ((ecma_char_t *) params[i], (ecma_length_t) strlen (params[i]));
  }
  return parser_parse_program (params[params_count - 1], strlen (params[params_count - 1]), true, false, out_opcodes_p);
} /* parser_parse_new_function */

/**
 * Tell parser to dump bytecode
 */
void
parser_set_show_opcodes (bool show_opcodes) /**< flag indicating if to dump bytecode */
{
  parser_show_opcodes = show_opcodes;
} /* parser_set_show_opcodes */
