parser_parse_statements (parser_context_t *context_p) /**< context */
{
  /* Statement parsing cannot be nested. */
  JERRY_ASSERT (context_p->last_statement.current_p == NULL);
  parser_stack_push_uint8 (context_p, PARSER_STATEMENT_START);
  parser_stack_iterator_init (context_p, &context_p->last_statement);

#if JERRY_DEBUGGER
  /* Set lexical enviroment for the debugger. */
  if (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED)
  {
    context_p->status_flags |= PARSER_LEXICAL_ENV_NEEDED;
    context_p->last_breakpoint_line = 0;
  }
#endif /* JERRY_DEBUGGER */

  while (context_p->token.type == LEXER_LITERAL
         && context_p->token.lit_location.type == LEXER_STRING_LITERAL)
  {
    lexer_lit_location_t lit_location;
    parser_strict_mode_type_t strict_mode = PARSER_USE_STRICT_NOT_FOUND;

    JERRY_ASSERT (context_p->stack_depth <= 1);
#ifndef JERRY_NDEBUG
    JERRY_ASSERT (context_p->context_stack_depth == context_p->stack_depth);
#endif /* !JERRY_NDEBUG */

    if (lexer_string_is_use_strict (context_p))
    {
      strict_mode = PARSER_USE_STRICT_FOUND;

      if (!(context_p->status_flags & PARSER_IS_STRICT))
      {
        /* The next token should be parsed in strict mode. */
        context_p->status_flags |= PARSER_IS_STRICT;
        strict_mode = PARSER_USE_STRICT_SET;
      }
    }

    lit_location = context_p->token.lit_location;
    lexer_next_token (context_p);

    if (!lexer_string_is_directive (context_p))
    {
      /* The string is part of an expression statement. */
      if (strict_mode == PARSER_USE_STRICT_SET)
      {
        context_p->status_flags &= (uint32_t) ~PARSER_IS_STRICT;
      }

#if JERRY_DEBUGGER
      if (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED)
      {
        JERRY_ASSERT (context_p->last_breakpoint_line == 0);

        parser_emit_cbc (context_p, CBC_BREAKPOINT_DISABLED);
        parser_flush_cbc (context_p);

        parser_append_breakpoint_info (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST, context_p->token.line);

        context_p->last_breakpoint_line = context_p->token.line;
      }
#endif /* JERRY_DEBUGGER */
#if JERRY_LINE_INFO
      parser_line_info_append (context_p, context_p->token.line, context_p->token.column);
#endif /* JERRY_LINE_INFO */

      lexer_construct_literal_object (context_p, &lit_location, LEXER_STRING_LITERAL);
      parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_LITERAL);
      /* The extra_value is used for saving the token. */
      context_p->token.extra_value = context_p->token.type;
      context_p->token.type = LEXER_EXPRESSION_START;
      break;
    }

#if JERRY_PARSER_DUMP_BYTE_CODE
    if (strict_mode == PARSER_USE_STRICT_SET && context_p->is_show_opcodes)
    {
      JERRY_DEBUG_MSG ("  Note: switch to strict mode\n\n");
    }
#endif /* JERRY_PARSER_DUMP_BYTE_CODE */

#if JERRY_ESNEXT
    if (strict_mode != PARSER_USE_STRICT_NOT_FOUND
        && (context_p->status_flags & PARSER_FUNCTION_HAS_COMPLEX_ARGUMENT))
    {
      parser_raise_error (context_p, PARSER_ERR_USE_STRICT_NOT_ALLOWED);
    }
#endif /* JERRY_ESNEXT */

    if (context_p->token.type == LEXER_SEMICOLON)
    {
      lexer_next_token (context_p);
    }

    /* The last directive prologue can be the result of the script. */
    if (!(context_p->status_flags & PARSER_IS_FUNCTION)
        && (context_p->token.type != LEXER_LITERAL
            || context_p->token.lit_location.type != LEXER_STRING_LITERAL))
    {
      lexer_construct_literal_object (context_p, &lit_location, LEXER_STRING_LITERAL);
      parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_LITERAL);
      parser_emit_cbc (context_p, CBC_POP_BLOCK);
      parser_flush_cbc (context_p);
      break;
    }
  }

  if (context_p->status_flags & PARSER_IS_STRICT
      && context_p->status_flags & PARSER_HAS_NON_STRICT_ARG)
  {
    parser_raise_error (context_p, PARSER_ERR_NON_STRICT_ARG_DEFINITION);
  }

  while (context_p->token.type != LEXER_EOS
         || context_p->stack_top_uint8 != PARSER_STATEMENT_START)
  {
#ifndef JERRY_NDEBUG
    JERRY_ASSERT (context_p->stack_depth == context_p->context_stack_depth);
#endif /* !JERRY_NDEBUG */

#if JERRY_ESNEXT
    JERRY_ASSERT (context_p->stack_top_uint8 != PARSER_STATEMENT_PRIVATE_SCOPE
                  && context_p->stack_top_uint8 != PARSER_STATEMENT_PRIVATE_CONTEXT);
#endif /* JERRY_ESNEXT */

#if JERRY_DEBUGGER
    if (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED
        && context_p->token.line != context_p->last_breakpoint_line
        && context_p->token.type != LEXER_SEMICOLON
        && context_p->token.type != LEXER_LEFT_BRACE
        && context_p->token.type != LEXER_RIGHT_BRACE
        && context_p->token.type != LEXER_KEYW_VAR
        && context_p->token.type != LEXER_KEYW_LET
        && context_p->token.type != LEXER_KEYW_CONST
        && context_p->token.type != LEXER_KEYW_FUNCTION
        && context_p->token.type != LEXER_KEYW_CASE
        && context_p->token.type != LEXER_KEYW_DEFAULT)
    {
      parser_emit_cbc (context_p, CBC_BREAKPOINT_DISABLED);
      parser_flush_cbc (context_p);

      parser_append_breakpoint_info (context_p, JERRY_DEBUGGER_BREAKPOINT_LIST, context_p->token.line);

      context_p->last_breakpoint_line = context_p->token.line;
    }
#endif /* JERRY_DEBUGGER */

#if JERRY_LINE_INFO
    if (context_p->token.type != LEXER_SEMICOLON
        && context_p->token.type != LEXER_LEFT_BRACE
        && context_p->token.type != LEXER_RIGHT_BRACE
        && context_p->token.type != LEXER_KEYW_VAR
        && context_p->token.type != LEXER_KEYW_LET
        && context_p->token.type != LEXER_KEYW_CONST
        && context_p->token.type != LEXER_KEYW_FUNCTION
        && context_p->token.type != LEXER_KEYW_CASE
        && context_p->token.type != LEXER_KEYW_DEFAULT)
    {
      parser_line_info_append (context_p, context_p->token.line, context_p->token.column);
    }
#endif /* JERRY_LINE_INFO */

    switch (context_p->token.type)
    {
      case LEXER_SEMICOLON:
      {
        break;
      }

      case LEXER_RIGHT_BRACE:
      {
        if (parser_statement_flags[context_p->stack_top_uint8] & PARSER_STATM_SINGLE_STATM)
        {
          parser_raise_error (context_p, PARSER_ERR_STATEMENT_EXPECTED);
        }
        break;
      }

      case LEXER_LEFT_BRACE:
      {
#if JERRY_ESNEXT
        if (context_p->next_scanner_info_p->source_p == context_p->source_p)
        {
          parser_push_block_context (context_p, false);
        }
        else
        {
          parser_stack_push_uint8 (context_p, PARSER_STATEMENT_BLOCK);
        }
#else /* !JERRY_ESNEXT */
        parser_stack_push_uint8 (context_p, PARSER_STATEMENT_BLOCK);
#endif /* JERRY_ESNEXT */

        parser_stack_iterator_init (context_p, &context_p->last_statement);
        lexer_next_token (context_p);
        continue;
      }

      case LEXER_KEYW_VAR:
#if JERRY_ESNEXT
      case LEXER_KEYW_LET:
      case LEXER_KEYW_CONST:
#endif /* JERRY_ESNEXT */
      {
        parser_parse_var_statement (context_p);
        break;
      }

#if JERRY_ESNEXT
      case LEXER_KEYW_CLASS:
      {
        parser_validate_lexical_context (context_p);
        parser_parse_class (context_p, true);
        goto consume_last_statement;
      }
#endif /* JERRY_ESNEXT */

#if JERRY_MODULE_SYSTEM
      case LEXER_KEYW_IMPORT:
      {
        parser_parse_import_statement (context_p);
        break;
      }

      case LEXER_KEYW_EXPORT:
      {
        if (parser_parse_export_statement (context_p))
        {
          goto consume_last_statement;
        }
        break;
      }
#endif /* JERRY_MODULE_SYSTEM */

      case LEXER_KEYW_FUNCTION:
      {
        parser_parse_function_statement (context_p);
        goto consume_last_statement;
      }

      case LEXER_KEYW_IF:
      {
        parser_parse_if_statement_start (context_p);
        continue;
      }

      case LEXER_KEYW_SWITCH:
      {
        parser_parse_switch_statement_start (context_p);
        continue;
      }

      case LEXER_KEYW_DO:
      {
        parser_do_while_statement_t do_while_statement;
        parser_loop_statement_t loop;

        JERRY_ASSERT (context_p->last_cbc_opcode == PARSER_CBC_UNAVAILABLE);

        do_while_statement.start_offset = context_p->byte_code_size;
        loop.branch_list_p = NULL;

        parser_stack_push (context_p, &do_while_statement, sizeof (parser_do_while_statement_t));
        parser_stack_push (context_p, &loop, sizeof (parser_loop_statement_t));
        parser_stack_push_uint8 (context_p, PARSER_STATEMENT_DO_WHILE);
        parser_stack_iterator_init (context_p, &context_p->last_statement);
        lexer_next_token (context_p);
        continue;
      }

      case LEXER_KEYW_WHILE:
      {
        parser_parse_while_statement_start (context_p);
        continue;
      }

      case LEXER_KEYW_FOR:
      {
        parser_parse_for_statement_start (context_p);
        continue;
      }

      case LEXER_KEYW_WITH:
      {
        parser_parse_with_statement_start (context_p);
        continue;
      }

      case LEXER_KEYW_TRY:
      {
        parser_try_statement_t try_statement;

        lexer_next_token (context_p);

        if (context_p->token.type != LEXER_LEFT_BRACE)
        {
          parser_raise_error (context_p, PARSER_ERR_LEFT_BRACE_EXPECTED);
        }

#ifndef JERRY_NDEBUG
        PARSER_PLUS_EQUAL_U16 (context_p->context_stack_depth, PARSER_TRY_CONTEXT_STACK_ALLOCATION);
#endif /* !JERRY_NDEBUG */

        try_statement.type = parser_try_block;
        parser_emit_cbc_ext_forward_branch (context_p,
                                            CBC_EXT_TRY_CREATE_CONTEXT,
                                            &try_statement.branch);

#if JERRY_ESNEXT
        try_statement.scope_stack_top = context_p->scope_stack_top;
        try_statement.scope_stack_reg_top = context_p->scope_stack_reg_top;

        if (context_p->next_scanner_info_p->source_p == context_p->source_p)
        {
          JERRY_ASSERT (context_p->next_scanner_info_p->type == SCANNER_TYPE_BLOCK);

          if (scanner_is_context_needed (context_p, PARSER_CHECK_BLOCK_CONTEXT))
          {
            parser_emit_cbc_ext (context_p, CBC_EXT_TRY_CREATE_ENV);
          }

          scanner_create_variables (context_p, SCANNER_CREATE_VARS_NO_OPTS);
        }
#endif /* JERRY_ESNEXT */

        parser_stack_push (context_p, &try_statement, sizeof (parser_try_statement_t));
        parser_stack_push_uint8 (context_p, PARSER_STATEMENT_TRY);
        parser_stack_iterator_init (context_p, &context_p->last_statement);
        lexer_next_token (context_p);
        continue;
      }

      case LEXER_KEYW_DEFAULT:
      {
        parser_parse_default_statement (context_p);
        continue;
      }

      case LEXER_KEYW_CASE:
      {
        parser_parse_case_statement (context_p);
        continue;
      }

      case LEXER_KEYW_BREAK:
      {
        parser_parse_break_statement (context_p);
        break;
      }

      case LEXER_KEYW_CONTINUE:
      {
        parser_parse_continue_statement (context_p);
        break;
      }

      case LEXER_KEYW_THROW:
      {
        lexer_next_token (context_p);
        if (context_p->token.flags & LEXER_WAS_NEWLINE)
        {
          parser_raise_error (context_p, PARSER_ERR_EXPRESSION_EXPECTED);
        }
        parser_parse_expression (context_p, PARSE_EXPR);
        parser_emit_cbc (context_p, CBC_THROW);
        break;
      }

      case LEXER_KEYW_RETURN:
      {
        if (!(context_p->status_flags & PARSER_IS_FUNCTION))
        {
          parser_raise_error (context_p, PARSER_ERR_INVALID_RETURN);
        }

        lexer_next_token (context_p);

        if ((context_p->token.flags & LEXER_WAS_NEWLINE)
            || context_p->token.type == LEXER_SEMICOLON
            || context_p->token.type == LEXER_EOS
            || context_p->token.type == LEXER_RIGHT_BRACE)
        {
#if JERRY_ESNEXT
          if (context_p->status_flags & PARSER_IS_ASYNC_FUNCTION)
          {
            parser_emit_cbc_ext (context_p, CBC_EXT_RETURN_UNDEFINED);
            break;
          }
#endif /* JERRY_ESNEXT */

          parser_emit_cbc (context_p, CBC_RETURN_FUNCTION_END);
          break;
        }

        parser_parse_expression (context_p, PARSE_EXPR);

        if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL)
        {
          context_p->last_cbc_opcode = CBC_RETURN_WITH_LITERAL;
          break;
        }

        parser_emit_cbc (context_p, CBC_RETURN);
        break;
      }

      case LEXER_KEYW_DEBUGGER:
      {
#if JERRY_DEBUGGER
        /* This breakpoint location is not reported to the
         * debugger, so it is impossible to disable it. */
        if (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED)
        {
          parser_emit_cbc (context_p, CBC_BREAKPOINT_ENABLED);
        }
#endif /* JERRY_DEBUGGER */
        lexer_next_token (context_p);
        break;
      }

      case LEXER_LITERAL:
      {
        if (context_p->token.lit_location.type == LEXER_IDENT_LITERAL)
        {
          if (JERRY_UNLIKELY (lexer_check_next_character (context_p, LIT_CHAR_COLON)))
          {
            parser_parse_label (context_p);
            lexer_consume_next_character (context_p);
            lexer_next_token (context_p);
            continue;
          }
#if JERRY_ESNEXT
          if (JERRY_UNLIKELY (lexer_token_is_let (context_p)))
          {
            if (context_p->next_scanner_info_p->source_p == context_p->source_p)
            {
              if (context_p->next_scanner_info_p->type == SCANNER_TYPE_LET_EXPRESSION)
              {
                scanner_release_next (context_p, sizeof (scanner_info_t));
              }

              if (context_p->status_flags & PARSER_IS_FUNCTION)
              {
                parser_parse_expression_statement (context_p, PARSE_EXPR);
                break;
              }

              parser_parse_block_expression (context_p, PARSE_EXPR);
              break;
            }

            context_p->token.type = LEXER_KEYW_LET;
            parser_parse_var_statement (context_p);
            break;
          }

          if (JERRY_UNLIKELY (lexer_token_is_async (context_p))
              && context_p->next_scanner_info_p->source_p == context_p->source_p)
          {
            bool is_statement = true;

            if (context_p->next_scanner_info_p->type == SCANNER_TYPE_FUNCTION)
            {
              is_statement = (context_p->next_scanner_info_p->u8_arg & SCANNER_FUNCTION_STATEMENT) != 0;

              JERRY_ASSERT (!is_statement || (context_p->next_scanner_info_p->u8_arg & SCANNER_FUNCTION_ASYNC));
            }
            else
            {
              JERRY_ASSERT (context_p->next_scanner_info_p->type == SCANNER_TYPE_ERR_ASYNC_FUNCTION);

              scanner_release_next (context_p, sizeof (scanner_info_t));
            }

            if (is_statement)
            {
              if (parser_statement_flags[context_p->stack_top_uint8] & PARSER_STATM_SINGLE_STATM)
              {
                parser_raise_error (context_p, PARSER_ERR_LEXICAL_SINGLE_STATEMENT);
              }

#if JERRY_FUNCTION_TO_STRING
              context_p->function_start_p = context_p->token.lit_location.char_p;
#endif /* JERRY_FUNCTION_TO_STRING */
              lexer_next_token (context_p);
              JERRY_ASSERT (context_p->token.type == LEXER_KEYW_FUNCTION);
              continue;
            }
          }
#endif /* JERRY_ESNEXT */
        }
        /* FALLTHRU */
      }

      default:
      {
        int options = PARSE_EXPR;

        if (context_p->token.type == LEXER_EXPRESSION_START)
        {
          /* Restore the token type form the extra_value. */
          context_p->token.type = context_p->token.extra_value;
          options |= PARSE_EXPR_HAS_LITERAL;
        }

        if (context_p->status_flags & PARSER_IS_FUNCTION)
        {
          parser_parse_expression_statement (context_p, options);
        }
        else
        {
          parser_parse_block_expression (context_p, options);
        }

        break;
      }
    }

    parser_flush_cbc (context_p);

    if (context_p->token.type == LEXER_RIGHT_BRACE)
    {
      if (context_p->stack_top_uint8 == PARSER_STATEMENT_BLOCK)
      {
        parser_stack_pop_uint8 (context_p);
        parser_stack_iterator_init (context_p, &context_p->last_statement);
        lexer_next_token (context_p);
      }
#if JERRY_ESNEXT
      else if (context_p->stack_top_uint8 == PARSER_STATEMENT_BLOCK_SCOPE
               || context_p->stack_top_uint8 == PARSER_STATEMENT_BLOCK_CONTEXT)
      {
        parser_pop_block_context (context_p);
        lexer_next_token (context_p);
      }
#endif /* JERRY_ESNEXT */
      else if (context_p->stack_top_uint8 == PARSER_STATEMENT_SWITCH
               || context_p->stack_top_uint8 == PARSER_STATEMENT_SWITCH_NO_DEFAULT)
      {
        int has_default = (context_p->stack_top_uint8 == PARSER_STATEMENT_SWITCH);
        parser_loop_statement_t loop;
        parser_switch_statement_t switch_statement;

        parser_stack_pop_uint8 (context_p);
        parser_stack_pop (context_p, &loop, sizeof (parser_loop_statement_t));
        parser_stack_pop (context_p, &switch_statement, sizeof (parser_switch_statement_t));
        parser_stack_iterator_init (context_p, &context_p->last_statement);

        JERRY_ASSERT (switch_statement.branch_list_p == NULL);

        if (!has_default)
        {
          parser_set_branch_to_current_position (context_p, &switch_statement.default_branch);
        }

        parser_set_breaks_to_current_position (context_p, loop.branch_list_p);
        lexer_next_token (context_p);

#if JERRY_ESNEXT
        if (context_p->stack_top_uint8 == PARSER_STATEMENT_PRIVATE_SCOPE
            || context_p->stack_top_uint8 == PARSER_STATEMENT_PRIVATE_CONTEXT)
        {
          parser_pop_block_context (context_p);
        }
#endif /* JERRY_ESNEXT */
      }
      else if (context_p->stack_top_uint8 == PARSER_STATEMENT_TRY)
      {
        parser_parse_try_statement_end (context_p);
      }
      else if (context_p->stack_top_uint8 == PARSER_STATEMENT_START)
      {
        if (context_p->status_flags & PARSER_IS_CLOSURE)
        {
#if JERRY_LINE_INFO
          if (context_p->line_info_p == NULL)
          {
            parser_line_info_append (context_p, context_p->token.line, context_p->token.column);
          }
#endif /* JERRY_LINE_INFO */

#if JERRY_FUNCTION_TO_STRING
          context_p->function_end_p = context_p->source_p;
#endif /* JERRY_FUNCTION_TO_STRING */

          parser_stack_pop_uint8 (context_p);
          context_p->last_statement.current_p = NULL;
          /* There is no lexer_next_token here, since the
           * next token belongs to the parent context. */
          return;
        }
        parser_raise_error (context_p, PARSER_ERR_INVALID_RIGHT_SQUARE);
      }
    }
    else if (context_p->token.type == LEXER_SEMICOLON)
    {
      lexer_next_token (context_p);
    }
    else if (context_p->token.type != LEXER_EOS
             && !(context_p->token.flags & LEXER_WAS_NEWLINE))
    {
      parser_raise_error (context_p, PARSER_ERR_SEMICOLON_EXPECTED);
    }

consume_last_statement:
    while (true)
    {
      switch (context_p->stack_top_uint8)
      {
        case PARSER_STATEMENT_LABEL:
        {
          parser_label_statement_t label;

          parser_stack_pop_uint8 (context_p);
          parser_stack_pop (context_p, &label, sizeof (parser_label_statement_t));
          parser_stack_iterator_init (context_p, &context_p->last_statement);

          parser_set_breaks_to_current_position (context_p, label.break_list_p);
          continue;
        }

        case PARSER_STATEMENT_IF:
        {
          if (parser_parse_if_statement_end (context_p))
          {
            break;
          }
          continue;
        }

        case PARSER_STATEMENT_ELSE:
        {
          parser_if_else_statement_t else_statement;

          parser_stack_pop_uint8 (context_p);
          parser_stack_pop (context_p, &else_statement, sizeof (parser_if_else_statement_t));
          parser_stack_iterator_init (context_p, &context_p->last_statement);

          parser_set_branch_to_current_position (context_p, &else_statement.branch);
          continue;
        }

        case PARSER_STATEMENT_DO_WHILE:
        {
          parser_parse_do_while_statement_end (context_p);
          if (context_p->token.type == LEXER_SEMICOLON)
          {
            lexer_next_token (context_p);
          }
          continue;
        }

        case PARSER_STATEMENT_WHILE:
        {
          parser_parse_while_statement_end (context_p);
          continue;
        }

        case PARSER_STATEMENT_FOR:
        {
          parser_parse_for_statement_end (context_p);
          continue;
        }

        case PARSER_STATEMENT_FOR_IN:
#if JERRY_ESNEXT
        case PARSER_STATEMENT_FOR_OF:
        case PARSER_STATEMENT_FOR_AWAIT_OF:
#endif /* JERRY_ESNEXT */
        {
          parser_for_in_of_statement_t for_in_of_statement;
          parser_loop_statement_t loop;

#if JERRY_ESNEXT
          uint8_t for_type = context_p->stack_top_uint8;
#endif /* JERRY_ESNEXT */

          parser_stack_pop_uint8 (context_p);
          parser_stack_pop (context_p, &loop, sizeof (parser_loop_statement_t));
          parser_stack_pop (context_p, &for_in_of_statement, sizeof (parser_for_in_of_statement_t));
          parser_stack_iterator_init (context_p, &context_p->last_statement);

          parser_set_continues_to_current_position (context_p, loop.branch_list_p);

          parser_flush_cbc (context_p);

          uint16_t stack_allocation = PARSER_FOR_IN_CONTEXT_STACK_ALLOCATION;
#if JERRY_ESNEXT
          if (for_type != PARSER_STATEMENT_FOR_IN)
          {
            stack_allocation = (for_type == PARSER_STATEMENT_FOR_OF ? PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION
                                                                    : PARSER_FOR_AWAIT_OF_CONTEXT_STACK_ALLOCATION);
          }
#endif /* JERRY_ESNEXT */

          PARSER_MINUS_EQUAL_U16 (context_p->stack_depth, stack_allocation);
#ifndef JERRY_NDEBUG
          PARSER_MINUS_EQUAL_U16 (context_p->context_stack_depth, stack_allocation);
#endif /* !JERRY_NDEBUG */

          cbc_ext_opcode_t opcode = CBC_EXT_BRANCH_IF_FOR_IN_HAS_NEXT;

#if JERRY_ESNEXT
          if (for_type != PARSER_STATEMENT_FOR_IN)
          {
            opcode = (for_type == PARSER_STATEMENT_FOR_OF ? CBC_EXT_BRANCH_IF_FOR_OF_HAS_NEXT
                                                          : CBC_EXT_BRANCH_IF_FOR_AWAIT_OF_HAS_NEXT);
          }
#endif /* JERRY_ESNEXT */

          parser_emit_cbc_ext_backward_branch (context_p, opcode, for_in_of_statement.start_offset);

          parser_set_breaks_to_current_position (context_p, loop.branch_list_p);
          parser_set_branch_to_current_position (context_p, &for_in_of_statement.branch);

#if JERRY_ESNEXT
          if (context_p->stack_top_uint8 == PARSER_STATEMENT_PRIVATE_SCOPE
              || context_p->stack_top_uint8 == PARSER_STATEMENT_PRIVATE_CONTEXT)
          {
            parser_pop_block_context (context_p);
          }
#endif /* JERRY_ESNEXT */
          continue;
        }

        case PARSER_STATEMENT_WITH:
        {
          parser_parse_with_statement_end (context_p);
          continue;
        }

        default:
        {
          break;
        }
      }
      break;
    }
  }

  parser_stack_pop_uint8 (context_p);
  context_p->last_statement.current_p = NULL;

  if (context_p->status_flags & PARSER_IS_CLOSURE)
  {
    parser_raise_error (context_p, PARSER_ERR_STATEMENT_EXPECTED);
  }

#if JERRY_LINE_INFO
  if (context_p->line_info_p == NULL)
  {
    parser_line_info_append (context_p, context_p->token.line, context_p->token.column);
  }
#endif /* JERRY_LINE_INFO */
} /* parser_parse_statements */


Fix parsing unterminated let statement (#4808)

This patch fixes #4754

JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik robert.fancsik@h-lab.eu

parser_parse_import_statement (parser_context_t *context_p) /**< parser context */
{
  JERRY_ASSERT (context_p->token.type == LEXER_KEYW_IMPORT);
  JERRY_ASSERT (context_p->module_names_p == NULL);

  if (lexer_check_next_characters (context_p, LIT_CHAR_LEFT_PAREN, LIT_CHAR_DOT))
  {
    if (context_p->status_flags & PARSER_IS_FUNCTION)
    {
      parser_parse_expression_statement (context_p, PARSE_EXPR);
      return;
    }

    parser_parse_block_expression (context_p, PARSE_EXPR);
    return;
  }

  parser_module_check_request_place (context_p);
  lexer_next_token (context_p);

  /* Check for a ModuleSpecifier*/
  if (context_p->token.type != LEXER_LITERAL
      || context_p->token.lit_location.type != LEXER_STRING_LITERAL)
  {
    if (!(context_p->token.type == LEXER_LEFT_BRACE
          || context_p->token.type == LEXER_MULTIPLY
          || (context_p->token.type == LEXER_LITERAL && context_p->token.lit_location.type == LEXER_IDENT_LITERAL)))
    {
      parser_raise_error (context_p, PARSER_ERR_LEFT_BRACE_MULTIPLY_LITERAL_EXPECTED);
    }

    if (context_p->token.type == LEXER_LITERAL)
    {
      /* Handle ImportedDefaultBinding */
      lexer_construct_literal_object (context_p, &context_p->token.lit_location, LEXER_IDENT_LITERAL);

      ecma_string_t *local_name_p = parser_new_ecma_string_from_literal (context_p->lit_object.literal_p);

      if (parser_module_check_duplicate_import (context_p, local_name_p))
      {
        ecma_deref_ecma_string (local_name_p);
        parser_raise_error (context_p, PARSER_ERR_DUPLICATED_IMPORT_BINDING);
      }

      ecma_string_t *import_name_p = ecma_get_magic_string (LIT_MAGIC_STRING_DEFAULT);
      parser_module_add_names_to_node (context_p, import_name_p, local_name_p);

      ecma_deref_ecma_string (local_name_p);
      ecma_deref_ecma_string (import_name_p);

      lexer_next_token (context_p);

      if (context_p->token.type == LEXER_COMMA)
      {
        lexer_next_token (context_p);
        if (context_p->token.type != LEXER_MULTIPLY
            && context_p->token.type != LEXER_LEFT_BRACE)
        {
          parser_raise_error (context_p, PARSER_ERR_LEFT_BRACE_MULTIPLY_EXPECTED);
        }
      }
      else if (!lexer_token_is_identifier (context_p, "from", 4))
      {
        parser_raise_error (context_p, PARSER_ERR_FROM_COMMA_EXPECTED);
      }
    }

    if (context_p->token.type == LEXER_MULTIPLY)
    {
      /* NameSpaceImport */
      lexer_next_token (context_p);
      if (!lexer_token_is_identifier (context_p, "as", 2))
      {
        parser_raise_error (context_p, PARSER_ERR_AS_EXPECTED);
      }

      lexer_next_token (context_p);
      if (context_p->token.type != LEXER_LITERAL)
      {
        parser_raise_error (context_p, PARSER_ERR_IDENTIFIER_EXPECTED);
      }

      lexer_construct_literal_object (context_p, &context_p->token.lit_location, LEXER_IDENT_LITERAL);

      ecma_string_t *local_name_p = parser_new_ecma_string_from_literal (context_p->lit_object.literal_p);

      if (parser_module_check_duplicate_import (context_p, local_name_p))
      {
        ecma_deref_ecma_string (local_name_p);
        parser_raise_error (context_p, PARSER_ERR_DUPLICATED_IMPORT_BINDING);
      }

      ecma_string_t *import_name_p = ecma_get_magic_string (LIT_MAGIC_STRING_ASTERIX_CHAR);

      parser_module_add_names_to_node (context_p, import_name_p, local_name_p);
      ecma_deref_ecma_string (local_name_p);
      ecma_deref_ecma_string (import_name_p);

      lexer_next_token (context_p);
    }
    else if (context_p->token.type == LEXER_LEFT_BRACE)
    {
      /* Handle NamedImports */
      parser_module_parse_import_clause (context_p);
    }

    if (!lexer_token_is_identifier (context_p, "from", 4))
    {
      parser_raise_error (context_p, PARSER_ERR_FROM_EXPECTED);
    }
    lexer_next_token (context_p);
  }

  parser_module_handle_module_specifier (context_p, NULL);
} /* parser_parse_import_statement */


parser_parse_export_statement (parser_context_t *context_p) /**< context */
{
  JERRY_ASSERT (context_p->token.type == LEXER_KEYW_EXPORT);
  JERRY_ASSERT (context_p->module_names_p == NULL);

  parser_module_check_request_place (context_p);

  bool consume_last_statement = false;

  lexer_next_token (context_p);
  switch (context_p->token.type)
  {
    case LEXER_KEYW_DEFAULT:
    {
      scanner_location_t location;
      scanner_get_location (&location, context_p);

      context_p->status_flags |= PARSER_MODULE_STORE_IDENT;

      lexer_next_token (context_p);

      if (context_p->token.type == LEXER_LITERAL
          && lexer_token_is_async (context_p)
          && context_p->next_scanner_info_p->source_p == context_p->source_p
          && context_p->next_scanner_info_p->type == SCANNER_TYPE_FUNCTION)
      {
#if JERRY_FUNCTION_TO_STRING
        context_p->function_start_p = context_p->token.lit_location.char_p;
#endif /* JERRY_FUNCTION_TO_STRING */
        lexer_next_token (context_p);
      }

      if (context_p->token.type == LEXER_KEYW_CLASS)
      {
        context_p->status_flags |= PARSER_MODULE_DEFAULT_CLASS_OR_FUNC;
        parser_parse_class (context_p, true);
        consume_last_statement = true;
      }
      else if (context_p->token.type == LEXER_KEYW_FUNCTION)
      {
        context_p->status_flags |= PARSER_MODULE_DEFAULT_CLASS_OR_FUNC;
        parser_parse_function_statement (context_p);
        consume_last_statement = true;
      }
      else
      {
        /* Assignment expression */
        scanner_set_location (context_p, &location);

        /* 15.2.3.5 Use the synthetic name '*default*' as the identifier. */
        lexer_construct_literal_object (context_p, &lexer_default_literal, lexer_default_literal.type);

        context_p->token.lit_location.type = LEXER_IDENT_LITERAL;
        parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_LITERAL);

        /* Do not overwrite this identifier. */
        context_p->status_flags &= (uint32_t) ~PARSER_MODULE_STORE_IDENT;
        context_p->module_identifier_lit_p = context_p->lit_object.literal_p;

        /* Fake an assignment to the default identifier */
        context_p->token.type = LEXER_ASSIGN;

        parser_parse_expression_statement (context_p, PARSE_EXPR_NO_COMMA | PARSE_EXPR_HAS_LITERAL);
      }

      ecma_string_t *name_p = parser_new_ecma_string_from_literal (context_p->module_identifier_lit_p);

      ecma_string_t *export_name_p = ecma_get_magic_string (LIT_MAGIC_STRING_DEFAULT);

      if (parser_module_check_duplicate_export (context_p, export_name_p))
      {
        ecma_deref_ecma_string (name_p);
        ecma_deref_ecma_string (export_name_p);
        parser_raise_error (context_p, PARSER_ERR_DUPLICATED_EXPORT_IDENTIFIER);
      }

      parser_module_add_names_to_node (context_p,
                                       export_name_p,
                                       name_p);
      ecma_deref_ecma_string (name_p);
      ecma_deref_ecma_string (export_name_p);
      break;
    }
    case LEXER_MULTIPLY:
    {
      lexer_next_token (context_p);

      ecma_module_node_t **target_node_list_p = &(JERRY_CONTEXT (module_current_p)->star_exports_p);

      if (lexer_token_is_identifier (context_p, "as", 2))
      {
        target_node_list_p = &(JERRY_CONTEXT (module_current_p)->indirect_exports_p);

        lexer_next_token (context_p);

        if (context_p->token.type != LEXER_LITERAL
            || context_p->token.lit_location.type != LEXER_IDENT_LITERAL)
        {
          parser_raise_error (context_p, PARSER_ERR_IDENTIFIER_EXPECTED);
        }

        lexer_construct_literal_object (context_p, &context_p->token.lit_location, LEXER_NEW_IDENT_LITERAL);

        lexer_literal_t *literal_p = PARSER_GET_LITERAL (context_p->lit_object.index);
        ecma_string_t *export_name_p = parser_new_ecma_string_from_literal (literal_p);

        if (parser_module_check_duplicate_export (context_p, export_name_p))
        {
          ecma_deref_ecma_string (export_name_p);
          parser_raise_error (context_p, PARSER_ERR_DUPLICATED_EXPORT_IDENTIFIER);
        }

        ecma_string_t *local_name_p = ecma_get_magic_string (LIT_MAGIC_STRING_ASTERIX_CHAR);
        parser_module_add_names_to_node (context_p, export_name_p, local_name_p);
        ecma_deref_ecma_string (export_name_p);

        lexer_next_token (context_p);
      }

      if (!lexer_token_is_identifier (context_p, "from", 4))
      {
        parser_raise_error (context_p, PARSER_ERR_FROM_EXPECTED);
      }

      lexer_next_token (context_p);
      parser_module_handle_module_specifier (context_p, target_node_list_p);
      return false;
    }
    case LEXER_KEYW_VAR:
    case LEXER_KEYW_LET:
    case LEXER_KEYW_CONST:
    {
      context_p->status_flags |= PARSER_MODULE_STORE_IDENT;
      parser_parse_var_statement (context_p);
      break;
    }
    case LEXER_KEYW_CLASS:
    {
      context_p->status_flags |= PARSER_MODULE_STORE_IDENT;
      parser_parse_class (context_p, true);
      consume_last_statement = true;
      break;
    }
    case LEXER_KEYW_FUNCTION:
    {
      context_p->status_flags |= PARSER_MODULE_STORE_IDENT;
      parser_parse_function_statement (context_p);
      consume_last_statement = true;
      break;
    }
    case LEXER_LEFT_BRACE:
    {
      parser_module_parse_export_clause (context_p);

      if (lexer_token_is_identifier (context_p, "from", 4))
      {
        lexer_next_token (context_p);
        parser_module_handle_module_specifier (context_p, &(JERRY_CONTEXT (module_current_p)->indirect_exports_p));
        return false;
      }
      break;
    }
    default:
    {
      parser_raise_error (context_p, PARSER_ERR_LEFT_BRACE_MULTIPLY_LITERAL_EXPECTED);
      break;
    }
  }

  context_p->status_flags &= (uint32_t) ~(PARSER_MODULE_DEFAULT_CLASS_OR_FUNC | PARSER_MODULE_STORE_IDENT);
  parser_module_append_names (context_p, &(JERRY_CONTEXT (module_current_p)->local_exports_p));

  return consume_last_statement;
} /* parser_parse_export_statement */


parser_parse_for_statement_start (parser_context_t *context_p) /**< context */
{
  parser_loop_statement_t loop;

  JERRY_ASSERT (context_p->token.type == LEXER_KEYW_FOR);
  lexer_next_token (context_p);

#if JERRY_ESNEXT
  bool is_for_await = false;

  if (context_p->token.type == LEXER_KEYW_AWAIT)
  {
    if (JERRY_UNLIKELY (context_p->token.lit_location.status_flags & LEXER_LIT_LOCATION_HAS_ESCAPE))
    {
      parser_raise_error (context_p, PARSER_ERR_INVALID_KEYWORD);
    }
    lexer_next_token (context_p);
    is_for_await = true;
  }
#endif /* JERRY_ESNEXT */

  if (context_p->token.type != LEXER_LEFT_PAREN)
  {
#if JERRY_ESNEXT
    if (context_p->token.type == LEXER_LITERAL
        && context_p->token.keyword_type == LEXER_KEYW_AWAIT
        && !(context_p->token.lit_location.status_flags & LEXER_LIT_LOCATION_HAS_ESCAPE))
    {
      parser_raise_error (context_p, PARSER_ERR_FOR_AWAIT_NO_ASYNC);
    }
#endif /* JERRY_ESNEXT */
    parser_raise_error (context_p, PARSER_ERR_LEFT_PAREN_EXPECTED);
  }

  if (context_p->next_scanner_info_p->source_p == context_p->source_p)
  {
    parser_for_in_of_statement_t for_in_of_statement;
    scanner_location_t start_location, end_location;

#if JERRY_ESNEXT
    JERRY_ASSERT (context_p->next_scanner_info_p->type == SCANNER_TYPE_FOR_IN
                  || context_p->next_scanner_info_p->type == SCANNER_TYPE_FOR_OF);

    bool is_for_in = (context_p->next_scanner_info_p->type == SCANNER_TYPE_FOR_IN);
    end_location = ((scanner_location_info_t *) context_p->next_scanner_info_p)->location;

    scanner_release_next (context_p, sizeof (scanner_location_info_t));

    scanner_get_location (&start_location, context_p);
    lexer_next_token (context_p);

    uint8_t token_type = LEXER_EOS;
    bool has_context = false;

    if (context_p->token.type == LEXER_KEYW_VAR
        || context_p->token.type == LEXER_KEYW_LET
        || context_p->token.type == LEXER_KEYW_CONST)
    {
      token_type = context_p->token.type;
      has_context = context_p->next_scanner_info_p->source_p == context_p->source_p;
      JERRY_ASSERT (!has_context || context_p->next_scanner_info_p->type == SCANNER_TYPE_BLOCK);
      scanner_get_location (&start_location, context_p);

      /* TODO: remove this after the pre-scanner supports strict mode detection. */
      if (context_p->next_scanner_info_p->source_p == context_p->source_p
          && context_p->next_scanner_info_p->type == SCANNER_TYPE_LET_EXPRESSION)
      {
        scanner_release_next (context_p, sizeof (scanner_info_t));
      }
    }
    else if (context_p->token.type == LEXER_LITERAL && lexer_token_is_let (context_p))
    {
      if (context_p->next_scanner_info_p->source_p == context_p->source_p
          && context_p->next_scanner_info_p->type == SCANNER_TYPE_LET_EXPRESSION)
      {
        scanner_release_next (context_p, sizeof (scanner_info_t));
      }
      else
      {
        token_type = LEXER_KEYW_LET;
        has_context = (context_p->next_scanner_info_p->source_p == context_p->source_p);
        scanner_get_location (&start_location, context_p);
      }
    }

    if (has_context)
    {
      has_context = parser_push_block_context (context_p, true);
    }

    scanner_set_location (context_p, &end_location);
#else /* !JERRY_ESNEXT */
    JERRY_ASSERT (context_p->next_scanner_info_p->type == SCANNER_TYPE_FOR_IN);

    bool is_for_in = true;
    scanner_get_location (&start_location, context_p);

    scanner_set_location (context_p, &((scanner_location_info_t *) context_p->next_scanner_info_p)->location);
    scanner_release_next (context_p, sizeof (scanner_location_info_t));
#endif /* JERRY_ESNEXT */

    /* The length of both 'in' and 'of' is two. */
    const uint8_t *source_end_p = context_p->source_p - 2;

    scanner_seek (context_p);

#if JERRY_ESNEXT
    if (is_for_in && is_for_await)
    {
      context_p->token.line = context_p->line;
      context_p->token.column = context_p->column - 2;
      parser_raise_error (context_p, PARSER_ERR_FOR_AWAIT_NO_OF);
    }
#endif /* JERRY_ESNEXT */

    lexer_next_token (context_p);
    int options = is_for_in ? PARSE_EXPR : PARSE_EXPR_LEFT_HAND_SIDE;
    parser_parse_expression (context_p, options);

    if (context_p->token.type != LEXER_RIGHT_PAREN)
    {
      parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED);
    }

#ifndef JERRY_NDEBUG
    PARSER_PLUS_EQUAL_U16 (context_p->context_stack_depth,
                           is_for_in ? PARSER_FOR_IN_CONTEXT_STACK_ALLOCATION
                                     : PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION);
#endif /* !JERRY_NDEBUG */

    cbc_ext_opcode_t init_opcode = CBC_EXT_FOR_IN_INIT;

#if JERRY_ESNEXT
    if (!is_for_in)
    {
      init_opcode = is_for_await ? CBC_EXT_FOR_AWAIT_OF_INIT : CBC_EXT_FOR_OF_INIT;
    }
#endif /* JERRY_ESNEXT */

    parser_emit_cbc_ext_forward_branch (context_p, init_opcode, &for_in_of_statement.branch);

    JERRY_ASSERT (context_p->last_cbc_opcode == PARSER_CBC_UNAVAILABLE);
    for_in_of_statement.start_offset = context_p->byte_code_size;

#if JERRY_ESNEXT
    if (has_context)
    {
      parser_emit_cbc_ext (context_p, CBC_EXT_CLONE_CONTEXT);
    }
#endif /* JERRY_ESNEXT */

    /* The expression parser must not read the 'in' or 'of' tokens. */
    scanner_get_location (&end_location, context_p);
    scanner_set_location (context_p, &start_location);

    const uint8_t *original_source_end_p = context_p->source_end_p;
    context_p->source_end_p = source_end_p;
    scanner_seek (context_p);

#if JERRY_ESNEXT
    if (token_type == LEXER_EOS)
    {
      lexer_next_token (context_p);

      if (context_p->token.type == LEXER_LEFT_SQUARE || context_p->token.type == LEXER_LEFT_BRACE)
      {
        token_type = context_p->token.type;
      }
    }
#else /* !JERRY_ESNEXT */
    lexer_next_token (context_p);

    uint8_t token_type = context_p->token.type;
#endif /* JERRY_ESNEXT */

    switch (token_type)
    {
#if JERRY_ESNEXT
      case LEXER_KEYW_LET:
      case LEXER_KEYW_CONST:
#endif /* JERRY_ESNEXT */
      case LEXER_KEYW_VAR:
      {
#if JERRY_ESNEXT
        if (lexer_check_next_characters (context_p, LIT_CHAR_LEFT_SQUARE, LIT_CHAR_LEFT_BRACE))
        {
          parser_emit_cbc_ext (context_p, is_for_in ? CBC_EXT_FOR_IN_GET_NEXT
                                                    : CBC_EXT_FOR_OF_GET_NEXT);

          parser_pattern_flags_t flags = (PARSER_PATTERN_BINDING | PARSER_PATTERN_TARGET_ON_STACK);

          if (context_p->next_scanner_info_p->source_p == (context_p->source_p + 1))
          {
            if (context_p->next_scanner_info_p->type == SCANNER_TYPE_INITIALIZER)
            {
              scanner_release_next (context_p, sizeof (scanner_location_info_t));
            }
            else
            {
              JERRY_ASSERT (context_p->next_scanner_info_p->type == SCANNER_TYPE_LITERAL_FLAGS);
              if (context_p->next_scanner_info_p->u8_arg & SCANNER_LITERAL_OBJECT_HAS_REST)
              {
                flags |= PARSER_PATTERN_HAS_REST_ELEMENT;
              }

              scanner_release_next (context_p, sizeof (scanner_info_t));
            }
          }

          if (token_type == LEXER_KEYW_LET)
          {
            flags |= PARSER_PATTERN_LET;
          }
          else if (token_type == LEXER_KEYW_CONST)
          {
            flags |= PARSER_PATTERN_CONST;
          }

          parser_parse_initializer_by_next_char (context_p, flags);
          break;
        }
#endif /* JERRY_ESNEXT */

        lexer_expect_identifier (context_p, LEXER_IDENT_LITERAL);

#if JERRY_ESNEXT
        if (context_p->token.keyword_type == LEXER_KEYW_LET
            && token_type != LEXER_KEYW_VAR)
        {
          parser_raise_error (context_p, PARSER_ERR_LEXICAL_LET_BINDING);
        }
#endif /* JERRY_ESNEXT */

        JERRY_ASSERT (context_p->token.type == LEXER_LITERAL
                      && context_p->token.lit_location.type == LEXER_IDENT_LITERAL);

        uint16_t literal_index = context_p->lit_object.index;
        lexer_next_token (context_p);

        if (context_p->token.type == LEXER_ASSIGN)
        {
#if JERRY_ESNEXT
          if ((context_p->status_flags & PARSER_IS_STRICT) || !is_for_in)
          {
            parser_raise_error (context_p, PARSER_ERR_FOR_IN_OF_DECLARATION);
          }
#endif /* JERRY_ESNEXT */
          parser_branch_t branch;

          /* Initialiser is never executed. */
          parser_emit_cbc_forward_branch (context_p, CBC_JUMP_FORWARD, &branch);
          lexer_next_token (context_p);
          parser_parse_expression_statement (context_p, PARSE_EXPR_NO_COMMA);
          parser_set_branch_to_current_position (context_p, &branch);
        }

        parser_emit_cbc_ext (context_p, is_for_in ? CBC_EXT_FOR_IN_GET_NEXT
                                                  : CBC_EXT_FOR_OF_GET_NEXT);
#if JERRY_ESNEXT
#ifndef JERRY_NDEBUG
        if (literal_index < PARSER_REGISTER_START
            && has_context
            && !scanner_literal_is_created (context_p, literal_index))
        {
          context_p->global_status_flags |= ECMA_PARSE_INTERNAL_FOR_IN_OFF_CONTEXT_ERROR;
        }
#endif /* !JERRY_NDEBUG */

        uint16_t opcode = (has_context ? CBC_ASSIGN_LET_CONST : CBC_ASSIGN_SET_IDENT);
        parser_emit_cbc_literal (context_p, opcode, literal_index);
#else /* !JERRY_ESNEXT */
        parser_emit_cbc_literal (context_p, CBC_ASSIGN_SET_IDENT, literal_index);
#endif /* JERRY_ESNEXT */
        break;
      }
#if JERRY_ESNEXT
      case LEXER_LEFT_BRACE:
      case LEXER_LEFT_SQUARE:
      {
        if (context_p->next_scanner_info_p->source_p == context_p->source_p
            && context_p->next_scanner_info_p->type == SCANNER_TYPE_LITERAL_FLAGS
            && (context_p->next_scanner_info_p->u8_arg & SCANNER_LITERAL_DESTRUCTURING_FOR))
        {
          parser_emit_cbc_ext (context_p, is_for_in ? CBC_EXT_FOR_IN_GET_NEXT
                                                    : CBC_EXT_FOR_OF_GET_NEXT);

          uint32_t flags = PARSER_PATTERN_TARGET_ON_STACK;

          if (context_p->next_scanner_info_p->u8_arg & SCANNER_LITERAL_OBJECT_HAS_REST)
          {
            flags |= PARSER_PATTERN_HAS_REST_ELEMENT;
          }

          scanner_release_next (context_p, sizeof (scanner_info_t));
          parser_parse_initializer (context_p, flags);
          /* Pop the value returned by GET_NEXT. */
          parser_emit_cbc (context_p, CBC_POP);
          break;
        }
        /* FALLTHRU */
      }
#endif /* JERRY_ESNEXT */
      default:
      {
        uint16_t opcode;

        parser_parse_expression (context_p, PARSE_EXPR_LEFT_HAND_SIDE);

        opcode = context_p->last_cbc_opcode;

        /* The CBC_EXT_FOR_IN_CREATE_CONTEXT flushed the opcode combiner. */
        JERRY_ASSERT (opcode != CBC_PUSH_TWO_LITERALS
                      && opcode != CBC_PUSH_THREE_LITERALS);

        opcode = parser_check_left_hand_side_expression (context_p, opcode);

        parser_emit_cbc_ext (context_p, is_for_in ? CBC_EXT_FOR_IN_GET_NEXT
                                                  : CBC_EXT_FOR_OF_GET_NEXT);
        parser_flush_cbc (context_p);

        context_p->last_cbc_opcode = opcode;
        break;
      }
    }

    if (context_p->token.type != LEXER_EOS)
    {
#if JERRY_ESNEXT
      parser_raise_error (context_p, is_for_in ? PARSER_ERR_IN_EXPECTED : PARSER_ERR_OF_EXPECTED);
#else /* !JERRY_ESNEXT */
      parser_raise_error (context_p, PARSER_ERR_IN_EXPECTED);
#endif /* JERRY_ESNEXT */
    }

    parser_flush_cbc (context_p);
    scanner_set_location (context_p, &end_location);
    context_p->source_end_p = original_source_end_p;
    lexer_next_token (context_p);

    loop.branch_list_p = NULL;

    parser_stack_push (context_p, &for_in_of_statement, sizeof (parser_for_in_of_statement_t));
    parser_stack_push (context_p, &loop, sizeof (parser_loop_statement_t));

    uint8_t for_type = PARSER_STATEMENT_FOR_IN;

#if JERRY_ESNEXT
    if (!is_for_in)
    {
      for_type = is_for_await ? PARSER_STATEMENT_FOR_AWAIT_OF : PARSER_STATEMENT_FOR_OF;
    }
#endif /* JERRY_ESNEXT */

    parser_stack_push_uint8 (context_p, for_type);
    parser_stack_iterator_init (context_p, &context_p->last_statement);
    return;
  }

  lexer_next_token (context_p);

  if (context_p->token.type != LEXER_SEMICOLON)
  {
#if JERRY_ESNEXT
    const uint8_t *source_p = context_p->source_p;
#endif /* JERRY_ESNEXT */

    switch (context_p->token.type)
    {
#if JERRY_ESNEXT
      case LEXER_LITERAL:
      {
        if (!lexer_token_is_let (context_p))
        {
          parser_parse_expression_statement (context_p, PARSE_EXPR);
          break;
        }

        /* FALLTHRU */
      }
      case LEXER_KEYW_LET:
      {
        if (context_p->next_scanner_info_p->source_p == context_p->source_p
            && context_p->next_scanner_info_p->type != SCANNER_TYPE_BLOCK)
        {
          if (context_p->next_scanner_info_p->type == SCANNER_TYPE_LET_EXPRESSION)
          {
            scanner_release_next (context_p, sizeof (scanner_info_t));
          }

          parser_parse_expression_statement (context_p, PARSE_EXPR);
          break;
        }

        context_p->token.type = LEXER_KEYW_LET;

        /* FALLTHRU */
      }
      case LEXER_KEYW_CONST:
      {
        if (context_p->next_scanner_info_p->source_p == source_p)
        {
          parser_push_block_context (context_p, true);
        }
        /* FALLTHRU */
      }
#endif /* JERRY_ESNEXT */
      case LEXER_KEYW_VAR:
      {
        parser_parse_var_statement (context_p);
        break;
      }
      default:
      {
        parser_parse_expression_statement (context_p, PARSE_EXPR);
        break;
      }
    }

    if (context_p->token.type != LEXER_SEMICOLON)
    {
      parser_raise_error (context_p, PARSER_ERR_SEMICOLON_EXPECTED);
    }
  }

#if JERRY_ESNEXT
  if (is_for_await)
  {
    parser_raise_error (context_p, PARSER_ERR_FOR_AWAIT_NO_OF);
  }
#endif /* JERRY_ESNEXT */

  JERRY_ASSERT (context_p->next_scanner_info_p->source_p != context_p->source_p
                || context_p->next_scanner_info_p->type == SCANNER_TYPE_FOR);

  if (context_p->next_scanner_info_p->source_p != context_p->source_p
      || ((scanner_for_info_t *) context_p->next_scanner_info_p)->end_location.source_p == NULL)
  {
    if (context_p->next_scanner_info_p->source_p == context_p->source_p)
    {
      /* Even though the scanning is failed, there might be valid statements
       * inside the for statement which depend on scanner info blocks. */
      scanner_release_next (context_p, sizeof (scanner_for_info_t));
    }

    /* The prescanner couldn't find the second semicolon or the closing paranthesis. */
    lexer_next_token (context_p);
    parser_parse_expression (context_p, PARSE_EXPR);

    if (context_p->token.type != LEXER_SEMICOLON)
    {
      parser_raise_error (context_p, PARSER_ERR_SEMICOLON_EXPECTED);
    }

    lexer_next_token (context_p);
    parser_parse_expression_statement (context_p, PARSE_EXPR);

    JERRY_ASSERT (context_p->token.type != LEXER_RIGHT_PAREN);
    parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED);
  }

  parser_for_statement_t for_statement;
  scanner_for_info_t *for_info_p = (scanner_for_info_t *) context_p->next_scanner_info_p;

  parser_emit_cbc_forward_branch (context_p, CBC_JUMP_FORWARD, &for_statement.branch);

  JERRY_ASSERT (context_p->last_cbc_opcode == PARSER_CBC_UNAVAILABLE);

  for_statement.start_offset = context_p->byte_code_size;
  scanner_get_location (&for_statement.condition_location, context_p);
  for_statement.expression_location = for_info_p->expression_location;

  scanner_set_location (context_p, &for_info_p->end_location);
  scanner_release_next (context_p, sizeof (scanner_for_info_t));
  scanner_seek (context_p);
  lexer_next_token (context_p);

  loop.branch_list_p = NULL;

  parser_stack_push (context_p, &for_statement, sizeof (parser_for_statement_t));
  parser_stack_push (context_p, &loop, sizeof (parser_loop_statement_t));
  parser_stack_push_uint8 (context_p, PARSER_STATEMENT_FOR);
  parser_stack_iterator_init (context_p, &context_p->last_statement);
} /* parser_parse_for_statement_start */


ok_png: Disallow multiple IHDR chunks (#15)

ok_inflater *ok_inflater_init(bool nowrap, ok_png_allocator allocator, void *allocator_user_data) {
    ok_inflater *inflater = allocator.alloc(allocator_user_data, sizeof(ok_inflater));
    if (inflater) {
        memset(inflater, 0, sizeof(ok_inflater));
        inflater->nowrap = nowrap;
        inflater->allocator = allocator;
        inflater->allocator_user_data = allocator_user_data;
        inflater->state = (nowrap ? OK_INFLATER_STATE_READY_FOR_NEXT_BLOCK :
                           OK_INFLATER_STATE_READY_FOR_HEAD);
        inflater->buffer = ok_alloc(inflater, BUFFER_SIZE);
        inflater->code_length_huffman = ok_alloc(inflater, sizeof(ok_inflater_huffman_tree));
        inflater->literal_huffman = ok_alloc(inflater, sizeof(ok_inflater_huffman_tree));
        inflater->distance_huffman = ok_alloc(inflater, sizeof(ok_inflater_huffman_tree));

        if (!inflater->buffer ||
            !inflater->code_length_huffman ||
            !inflater->literal_huffman ||
            !inflater->distance_huffman) {
            ok_inflater_free(inflater);
            inflater = NULL;
        }
    }
    return inflater;
}


static void ok_png_transform_scanline(ok_png_decoder *decoder, const uint8_t *src, uint32_t width) {
    ok_png *png = decoder->png;
    const bool dst_flip_y = (decoder->decode_flags & OK_PNG_FLIP_Y) != 0;
    uint8_t *dst_start;
    uint8_t *dst_end;
    if (decoder->interlace_method == 0) {
        const uint32_t dst_y =
            (dst_flip_y ? (png->height - decoder->scanline - 1) : decoder->scanline);
        dst_start = png->data + (dst_y * png->stride);
    } else if (decoder->interlace_pass == 7) {
        const uint32_t t_scanline = decoder->scanline * 2 + 1;
        const uint32_t dst_y = dst_flip_y ? (png->height - t_scanline - 1) : t_scanline;
        dst_start = png->data + (dst_y * png->stride);
    } else {
        dst_start = decoder->temp_data_row;
    }
    dst_end = dst_start + width * png->bpp;

    const int c = decoder->color_type;
    const int d = decoder->bit_depth;
    const bool t = decoder->has_single_transparent_color;
    const bool has_full_alpha = (c == OK_PNG_COLOR_TYPE_GRAYSCALE_WITH_ALPHA ||
                                 c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA);
    const bool src_is_premultiplied = decoder->is_ios_format;
    const bool dst_is_premultiplied = (decoder->decode_flags & OK_PNG_PREMULTIPLIED_ALPHA) != 0;
    const bool src_is_bgr = decoder->is_ios_format;
    const bool dst_is_bgr = (decoder->decode_flags & OK_PNG_COLOR_FORMAT_BGRA) != 0;
    bool should_byteswap = ((c == OK_PNG_COLOR_TYPE_RGB || c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA) &&
                            src_is_bgr != dst_is_bgr);

    // Simple transforms
    if (c == OK_PNG_COLOR_TYPE_GRAYSCALE && d == 8 && !t) {
        uint8_t *dst = dst_start;
        while (dst < dst_end) {
            uint8_t v = *src++;
            *dst++ = v;
            *dst++ = v;
            *dst++ = v;
            *dst++ = 0xff;
        }
    } else if (c == OK_PNG_COLOR_TYPE_PALETTE && d == 8) {
        uint8_t *dst = dst_start;
        const uint8_t *palette = decoder->palette;
        while (dst < dst_end) {
            memcpy(dst, palette + *src * 4, 4);
            dst += 4;
            src++;
        }
    } else if (c == OK_PNG_COLOR_TYPE_RGB && d == 8 && !t) {
        if (should_byteswap) {
            uint8_t *dst = dst_start;
            while (dst < dst_end) {
                dst[0] = src[2];
                dst[1] = src[1];
                dst[2] = src[0];
                dst[3] = 0xff;
                src += 3;
                dst += 4;
            }
            should_byteswap = false;
        } else {
            uint8_t *dst = dst_start;
            while (dst < dst_end) {
                memcpy(dst, src, 3);
                dst[3] = 0xff;
                src += 3;
                dst += 4;
            }
        }
    } else if (c == OK_PNG_COLOR_TYPE_GRAYSCALE_WITH_ALPHA && d == 8) {
        uint8_t *dst = dst_start;
        while (dst < dst_end) {
            uint8_t v = *src++;
            uint8_t a = *src++;
            *dst++ = v;
            *dst++ = v;
            *dst++ = v;
            *dst++ = a;
        }
    } else if (c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA && d == 8) {
        memcpy(dst_start, src, width * 4);
    } else {
        // Complex transforms: 1-, 2-, 4- and 16-bit, and 8-bit with single-color transparency.
        const uint8_t *palette = decoder->palette;
        const int bitmask = (1 << d) - 1;
        int bit = 8 - d;
        uint16_t tr = decoder->single_transparent_color_key[0];
        uint16_t tg = decoder->single_transparent_color_key[1];
        uint16_t tb = decoder->single_transparent_color_key[2];
        if (d <= 8) {
            tr = (uint16_t)((tr & bitmask) * (255 / bitmask));
            tg = (uint16_t)((tg & bitmask) * (255 / bitmask));
            tb = (uint16_t)((tb & bitmask) * (255 / bitmask));
        }
        uint8_t *dst = dst_start;
        while (dst < dst_end) {
            uint16_t r = 0;
            uint16_t g = 0;
            uint16_t b = 0;
            uint16_t a = 0xffff;

            if (d < 8) {
                if (bit < 0) {
                    bit = 8 - d;
                    src++;
                }
                int v = (*src >> bit) & bitmask;
                if (c == OK_PNG_COLOR_TYPE_GRAYSCALE) {
                    r = g = b = (uint16_t)(v * (255 / bitmask));
                } else {
                    const uint8_t *psrc = palette + (v * 4);
                    r = *psrc++;
                    g = *psrc++;
                    b = *psrc++;
                    a = *psrc++;
                }
                bit -= d;
            } else if (d == 8) {
                if (c == OK_PNG_COLOR_TYPE_GRAYSCALE) {
                    r = g = b = *src++;
                } else if (c == OK_PNG_COLOR_TYPE_PALETTE) {
                    const uint8_t *psrc = palette + (*src++ * 4);
                    r = *psrc++;
                    g = *psrc++;
                    b = *psrc++;
                    a = *psrc++;
                } else if (c == OK_PNG_COLOR_TYPE_GRAYSCALE_WITH_ALPHA) {
                    r = g = b = *src++;
                    a = *src++;
                } else if (c == OK_PNG_COLOR_TYPE_RGB) {
                    r = *src++;
                    g = *src++;
                    b = *src++;
                } else if (c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA) {
                    r = *src++;
                    g = *src++;
                    b = *src++;
                    a = *src++;
                }
            } else if (d == 16) {
                if (c == OK_PNG_COLOR_TYPE_GRAYSCALE) {
                    r = g = b = readBE16(src);
                    src += 2;
                } else if (c == OK_PNG_COLOR_TYPE_GRAYSCALE_WITH_ALPHA) {
                    r = g = b = readBE16(src);
                    a = readBE16(src + 2);
                    src += 4;
                } else if (c == OK_PNG_COLOR_TYPE_RGB) {
                    r = readBE16(src);
                    g = readBE16(src + 2);
                    b = readBE16(src + 4);
                    src += 6;
                } else if (c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA) {
                    r = readBE16(src);
                    g = readBE16(src + 2);
                    b = readBE16(src + 4);
                    a = readBE16(src + 6);
                    src += 8;
                }
            }

            if (t && r == tr && g == tg && b == tb) {
                a = 0;
                if (dst_is_premultiplied) {
                    r = b = g = 0;
                }
            }

            if (d == 16) {
                // This is libpng's formula for scaling 16-bit to 8-bit
                r = (r * 255 + 32895) >> 16;
                g = (g * 255 + 32895) >> 16;
                b = (b * 255 + 32895) >> 16;
                a = (a * 255 + 32895) >> 16;
            }

            if (should_byteswap) {
                *dst++ = (uint8_t)b;
                *dst++ = (uint8_t)g;
                *dst++ = (uint8_t)r;
                *dst++ = (uint8_t)a;
            } else {
                *dst++ = (uint8_t)r;
                *dst++ = (uint8_t)g;
                *dst++ = (uint8_t)b;
                *dst++ = (uint8_t)a;
            }
        }
        should_byteswap = false;
    }

    // Color format convert: Premultiply, un-premultiply, and swap if needed
    if (should_byteswap) {
        if (has_full_alpha && src_is_premultiplied && !dst_is_premultiplied) {
            // Convert from BGRA_PRE to RGBA
            for (uint8_t *dst = dst_start; dst < dst_end; dst += 4) {
                const uint8_t v = dst[0];
                dst[0] = dst[2];
                dst[2] = v;
                ok_png_unpremultiply(dst);
            }
        } else if (has_full_alpha && !src_is_premultiplied && dst_is_premultiplied) {
            // Convert from BGRA to RGBA_PRE
            for (uint8_t *dst = dst_start; dst < dst_end; dst += 4) {
                const uint8_t v = dst[0];
                dst[0] = dst[2];
                dst[2] = v;
                ok_png_premultiply(dst);
            }
        } else {
            // Convert from BGRA to RGBA (or BGRA_PRE to RGBA_PRE)
            for (uint8_t *dst = dst_start; dst < dst_end; dst += 4) {
                const uint8_t v = dst[0];
                dst[0] = dst[2];
                dst[2] = v;
            }
        }
    } else if (has_full_alpha) {
        if (src_is_premultiplied && !dst_is_premultiplied) {
            // Convert from RGBA_PRE to RGBA
            for (uint8_t *dst = dst_start; dst < dst_end; dst += 4) {
                ok_png_unpremultiply(dst);
            }
        } else if (!src_is_premultiplied && dst_is_premultiplied) {
            // Convert from RGBA to RGBA_PRE
            for (uint8_t *dst = dst_start; dst < dst_end; dst += 4) {
                ok_png_premultiply(dst);
            }
        } else {
            // Do nothing: Already in correct format, RGBA or RGBA_PRE
        }
    }

    // If interlaced, copy from the temp buffer
    if (decoder->interlace_method == 1 && decoder->interlace_pass < 7) {
        const int i = decoder->interlace_pass;
        const uint32_t s = decoder->scanline;
        //                                       1      2      3      4      5      6      7
        static const uint32_t dst_x[]  = {0,     0,     4,     0,     2,     0,     1,     0 };
        static const uint32_t dst_dx[] = {0,     8,     8,     4,     4,     2,     2,     1 };
               const uint32_t dst_y[]  = {0,   s*8,   s*8, 4+s*8,   s*4, 2+s*4,   s*2, 1+s*2 };

        uint32_t x = dst_x[i];
        uint32_t y = dst_y[i];
        uint32_t dx = 4 * dst_dx[i];
        if (dst_flip_y) {
            y = (png->height - y - 1);
        }

        src = dst_start;
        uint8_t *src_end = dst_end;
        uint8_t *dst = png->data + (y * png->stride) + (x * 4);
        while (src < src_end) {
            memcpy(dst, src, 4);
            dst += dx;
            src += 4;
        }
    }
}


static bool ok_inflater_distance_with_tree(ok_inflater *inflater,
                                           const ok_inflater_huffman_tree *tree) {
    if (inflater->state_count < 0) {
        inflater->state_count = ok_inflater_decode_length(inflater, inflater->huffman_code);
        if (inflater->state_count < 0) {
            // Needs input
            return false;
        }
        inflater->huffman_code = -1;
    }
    if (inflater->state_distance < 0) {
        inflater->state_distance = ok_inflater_decode_distance(inflater, tree);
        if (inflater->state_distance < 0) {
            // Needs input
            return false;
        }
    }

    // Copy len bytes from offset to buffer_end_pos
    int buffer_offset = (inflater->buffer_end_pos - inflater->state_distance) & BUFFER_SIZE_MASK;
    if (inflater->state_distance == 1) {
        // Optimization: can use memset
        int n = inflater->state_count;
        int n2 = ok_inflater_write_byte_n(inflater, inflater->buffer[buffer_offset], n);
        inflater->state_count -= n2;
        if (n2 != n) {
            // Full buffer
            return false;
        }
    } else if (buffer_offset + inflater->state_count < BUFFER_SIZE) {
        // Optimization: the offset won't wrap
        int bytes_copyable = inflater->state_distance;
        while (inflater->state_count > 0) {
            int n = min(inflater->state_count, bytes_copyable);
            int n2 = ok_inflater_write_bytes(inflater, inflater->buffer + buffer_offset, n);
            inflater->state_count -= n2;
            bytes_copyable += n2;
            if (n2 != n) {
                // Full buffer
                return false;
            }
        }
    } else {
        // This could be optimized, but it happens rarely, so it's probably not worth it
        while (inflater->state_count > 0) {
            int n = min(inflater->state_count, inflater->state_distance);
            n = min(n, (BUFFER_SIZE - buffer_offset));
            int n2 = ok_inflater_write_bytes(inflater, inflater->buffer + buffer_offset, n);
            inflater->state_count -= n2;
            buffer_offset = (buffer_offset + n2) & BUFFER_SIZE_MASK;
            if (n2 != n) {
                // Full buffer
                return false;
            }
        }
    }
    return true;
}


static int ok_inflater_decode_distance(ok_inflater *inflater,
                                       const ok_inflater_huffman_tree *tree) {
    if (!ok_inflater_load_bits(inflater, tree->bits)) {
        return -1;
    }
    uint32_t p = ok_inflater_peek_bits(inflater, tree->bits);
    uint16_t tree_value = tree->lookup_table[p];
    int value = tree_value & VALUE_BIT_MASK;
    unsigned int value_bits = tree_value >> VALUE_BITS;
    if (value < 4) {
        ok_inflater_read_bits(inflater, value_bits);
        return value + 1;
    } else if (value >= OK_INFLATER_DISTANCE_TABLE_LENGTH) {
        ok_inflater_error(inflater, "Invalid distance");
        return -1;
    } else {
        unsigned int extra_bits = (unsigned int)((value >> 1) - 1);
        if (!ok_inflater_can_read_bits(inflater, value_bits + extra_bits)) {
            return -1;
        }
        // Make sure to load no more than 32 bits at once
        ok_inflater_read_bits(inflater, value_bits);
        ok_inflater_load_bits(inflater, extra_bits);
        int d = (int)ok_inflater_read_bits(inflater, extra_bits);
        return OK_INFLATER_DISTANCE_TABLE[value] + d;
    }
}


static void ok_png_set_error(ok_png *png, ok_png_error error_code) {
    if (png) {
        png->width = 0;
        png->height = 0;
        png->error_code = error_code;
    }
}


static bool ok_png_read_header(ok_png_decoder *decoder, uint32_t chunk_length) {
    ok_png *png = decoder->png;
    if (chunk_length != 13) {
        ok_png_error(png, OK_PNG_ERROR_INVALID, "Invalid IHDR chunk length");
        return false;
    }
    uint8_t chunk_data[13];
    if (!ok_read(decoder, chunk_data, sizeof(chunk_data))) {
        return false;
    }
    png->width = readBE32(chunk_data);
    png->height = readBE32(chunk_data + 4);
    png->bpp = 4; // Always decoding to 32-bit color
    decoder->bit_depth = chunk_data[8];
    decoder->color_type = chunk_data[9];
    uint8_t compression_method = chunk_data[10];
    uint8_t filter_method = chunk_data[11];
    decoder->interlace_method = chunk_data[12];
    uint64_t stride = (uint64_t)png->width * png->bpp;

    if (compression_method != 0) {
        ok_png_error(png, OK_PNG_ERROR_INVALID, "Invalid compression method");
        return false;
    } else if (filter_method != 0) {
        ok_png_error(png, OK_PNG_ERROR_INVALID, "Invalid filter method");
        return false;
    } else if (decoder->interlace_method != 0 && decoder->interlace_method != 1) {
        ok_png_error(png, OK_PNG_ERROR_INVALID, "Invalid interlace method");
        return false;
    } else if (stride > UINT32_MAX) {
        ok_png_error(png, OK_PNG_ERROR_UNSUPPORTED, "Width too large");
        return false;
    }

    const int c = decoder->color_type;
    const int b = decoder->bit_depth;
    const bool valid =
        (c == OK_PNG_COLOR_TYPE_GRAYSCALE && (b == 1 || b == 2 || b == 4 || b == 8 || b == 16)) ||
        (c == OK_PNG_COLOR_TYPE_RGB && (b == 8 || b == 16)) ||
        (c == OK_PNG_COLOR_TYPE_PALETTE && (b == 1 || b == 2 || b == 4 || b == 8)) ||
        (c == OK_PNG_COLOR_TYPE_GRAYSCALE_WITH_ALPHA && (b == 8 || b == 16)) ||
        (c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA && (b == 8 || b == 16));

    if (!valid) {
        ok_png_error(png, OK_PNG_ERROR_INVALID, "Invalid combination of color type and bit depth");
        return false;
    }

    png->stride = (uint32_t)stride;
    png->has_alpha = (c == OK_PNG_COLOR_TYPE_GRAYSCALE_WITH_ALPHA ||
                      c == OK_PNG_COLOR_TYPE_RGB_WITH_ALPHA);
    decoder->interlace_pass = 0;
    decoder->ready_for_next_interlace_pass = true;
    return true;
}


static inline int ok_png_paeth_predictor(int a, int b, int c) {
    int p = a + b - c;
    int pa = abs(p - a);
    int pb = abs(p - b);
    int pc = abs(p - c);
    if (pa <= pb && pa <= pc) {
        return a;
    } else if (pb <= pc) {
        return b;
    } else {
        return c;
    }
}


static inline size_t ok_inflater_flush(ok_inflater *inflater, uint8_t *dst, size_t len) {
    size_t bytes_remaining = len;
    while (bytes_remaining > 0) {
        size_t n = min(bytes_remaining, ok_inflater_can_flush(inflater));
        if (n == 0) {
            return len - bytes_remaining;
        }
        memcpy(dst, inflater->buffer + inflater->buffer_start_pos, n);
        inflater->buffer_start_pos += n;
        bytes_remaining -= n;
        dst += n;
    }
    return len;
}


static bool ok_seek(ok_png_decoder *decoder, long length) {
    if (decoder->input.seek(decoder->input_user_data, length)) {
        return true;
    } else {
        ok_png_error(decoder->png, OK_PNG_ERROR_IO, "Seek error: error calling input function.");
        return false;
    }
}


static bool ok_inflater_noop(ok_inflater *inflater) {
    (void)inflater;
    return false;
}


static void ok_inflater_make_huffman_tree_from_array(ok_inflater_huffman_tree *tree,
                                                     const uint8_t *code_length, int length) {
    tree->bits = 1;

    // Count the number of codes for each code length.
    // Let code_length_count[n] be the number of codes of length n, n >= 1.
    unsigned int code_length_count[MAX_CODE_LENGTH];
    int i;
    for (i = 0; i < MAX_CODE_LENGTH; i++) {
        code_length_count[i] = 0;
    }
    for (i = 0; i < length; i++) {
        code_length_count[code_length[i]]++;
    }

    // Find the numerical value of the smallest code for each code length:
    unsigned int next_code[MAX_CODE_LENGTH];
    unsigned int code = 0;
    for (i = 1; i < MAX_CODE_LENGTH; i++) {
        code = (code + code_length_count[i - 1]) << 1;
        next_code[i] = code;
        if (code_length_count[i] != 0) {
            tree->bits = (unsigned int)i;
        }
    }

    // Init lookup table
    const unsigned int max = 1 << tree->bits;
    memset(tree->lookup_table, 0, sizeof(tree->lookup_table[0]) * max);

    // Assign numerical values to all codes, using consecutive values for all
    // codes of the same length with the base values determined at step 2.
    // Codes that are never used (which have a bit length of zero) must not be
    // assigned a value.
    for (i = 0; i < length; i++) {
        unsigned int len = code_length[i];
        if (len != 0) {
            code = next_code[len];
            next_code[len]++;

            unsigned int value = (unsigned int)i | (len << VALUE_BITS);
            tree->lookup_table[ok_inflater_reverse_bits(code, len)] = (uint16_t)value;
        }
    }

    // Fill in the missing parts of the lookup table
    int next_limit = 1;
    int num_bits = 0;
    int mask = 0;
    for (i = 1; i < (int)max; i++) {
        if (i == next_limit) {
            mask = (1 << num_bits) - 1;
            num_bits++;
            next_limit <<= 1;
        }
        if (tree->lookup_table[i] == 0) {
            tree->lookup_table[i] = tree->lookup_table[i & mask];
        }
    }
}


static inline void ok_inflater_write_byte(ok_inflater *inflater, const uint8_t b) {
    inflater->buffer[inflater->buffer_end_pos & BUFFER_SIZE_MASK] = b;
    inflater->buffer_end_pos++;
}


static uint32_t ok_png_get_height_for_pass(const ok_png_decoder *decoder) {
    const uint32_t h = decoder->png->height;
    if (decoder->interlace_method == 0) {
        return h;
    }

    switch (decoder->interlace_pass) {
        case 1: return (h + 7) / 8;
        case 2: return (h + 7) / 8;
        case 3: return (h + 3) / 8;
        case 4: return (h + 3) / 4;
        case 5: return (h + 1) / 4;
        case 6: return (h + 1) / 2;
        case 7: return h / 2;
        default: return 0;
    }
}


static inline uint16_t ok_inflater_can_flush(const ok_inflater *inflater) {
    if (inflater->buffer_start_pos <= inflater->buffer_end_pos) {
        return inflater->buffer_end_pos - inflater->buffer_start_pos;
    } else {
        return -inflater->buffer_start_pos;
    }
}


static inline int ok_inflater_write_byte_n(ok_inflater *inflater, const uint8_t b, int len) {
    int bytes_remaining = len;
    while (bytes_remaining > 0) {
        int n = min(bytes_remaining, ok_inflater_can_write(inflater));
        if (n == 0) {
            return len - bytes_remaining;
        }
        memset(inflater->buffer + inflater->buffer_end_pos, b, (size_t)n);
        inflater->buffer_end_pos += n;
        bytes_remaining -= n;
    }
    return len;
}


static uint32_t ok_png_get_width_for_pass(const ok_png_decoder *decoder) {
    const uint32_t w = decoder->png->width;
    if (decoder->interlace_method == 0) {
        return w;
    }

    switch (decoder->interlace_pass) {
        case 1: return (w + 7) / 8;
        case 2: return (w + 3) / 8;
        case 3: return (w + 3) / 4;
        case 4: return (w + 1) / 4;
        case 5: return (w + 1) / 2;
        case 6: return w / 2;
        case 7: return w;
        default: return 0;
    }
}


static bool ok_png_read_data(ok_png_decoder *decoder, uint32_t bytes_remaining) {
    ok_png *png = decoder->png;
    size_t inflate_buffer_size = 64 * 1024;
    size_t num_passes = decoder->interlace_method == 0 ? 1 : 7;
    uint8_t bits_per_pixel = decoder->bit_depth * OK_PNG_SAMPLES_PER_PIXEL[decoder->color_type];
    uint8_t bytes_per_pixel = (bits_per_pixel + 7) / 8;
    uint64_t max_bytes_per_scanline = 1 + ((uint64_t)png->width * bits_per_pixel + 7) / 8;
    size_t platform_max_bytes_per_scanline = (size_t)max_bytes_per_scanline;

    // Create buffers
    if (!png->data) {
        if (decoder->allocator.image_alloc) {
            decoder->allocator.image_alloc(decoder->allocator_user_data,
                                           png->width, png->height, png->bpp,
                                           &png->data, &png->stride);
        } else {
            uint64_t size = (uint64_t)png->stride * png->height;
            size_t platform_size = (size_t)size;
            if (platform_size == size) {
                png->data = ok_alloc(decoder, platform_size);
            }
        }
        if (!png->data) {
            ok_png_error(png, OK_PNG_ERROR_ALLOCATION, "Couldn't allocate memory for image");
            return false;
        }
        if (png->stride < png->width * png->bpp) {
            ok_png_error(png, OK_PNG_ERROR_API, "Invalid stride");
            return false;
        }
    }
    if (!decoder->prev_scanline) {
        if (max_bytes_per_scanline == platform_max_bytes_per_scanline) {
            decoder->prev_scanline = ok_alloc(decoder, platform_max_bytes_per_scanline);
        }
    }
    if (!decoder->curr_scanline) {
        if (max_bytes_per_scanline == platform_max_bytes_per_scanline) {
            decoder->curr_scanline = ok_alloc(decoder, platform_max_bytes_per_scanline);
        }
    }
    if (!decoder->inflate_buffer) {
        decoder->inflate_buffer = ok_alloc(decoder, inflate_buffer_size);
    }
    if (decoder->interlace_method == 1 && !decoder->temp_data_row) {
        decoder->temp_data_row = ok_alloc(decoder, png->width * png->bpp);
    }
    if (!decoder->curr_scanline || !decoder->prev_scanline || !decoder->inflate_buffer ||
        (decoder->interlace_method == 1 && !decoder->temp_data_row)) {
        ok_png_error(png, OK_PNG_ERROR_ALLOCATION, "Couldn't allocate buffers");
        return false;
    }

    // Setup inflater
    if (!decoder->inflater) {
        decoder->inflater = ok_inflater_init(decoder->is_ios_format,
                                             decoder->allocator, decoder->allocator_user_data);
        if (!decoder->inflater) {
            ok_png_error(png, OK_PNG_ERROR_ALLOCATION, "Couldn't init inflater");
            return false;
        }
    }

    // Sanity check - this happened with one file in the PNG suite
    if (decoder->decoding_completed) {
        if (bytes_remaining > 0) {
            return ok_seek(decoder, (long)bytes_remaining);
        } else {
            return true;
        }
    }

    // Read data
    uint32_t curr_width = ok_png_get_width_for_pass(decoder);
    uint32_t curr_height = ok_png_get_height_for_pass(decoder);
    size_t curr_bytes_per_scanline = (size_t)(1 + ((uint64_t)curr_width * bits_per_pixel + 7) / 8);
    while (true) {
        // Setup pass
        while (decoder->ready_for_next_interlace_pass) {
            decoder->ready_for_next_interlace_pass = false;
            decoder->scanline = 0;
            decoder->interlace_pass++;
            if (decoder->interlace_pass == num_passes + 1) {
                // Done decoding - skip any remaining chunk data
                decoder->decoding_completed = true;
                if (bytes_remaining > 0) {
                    return ok_seek(decoder, (long)bytes_remaining);
                } else {
                    return true;
                }
            }
            curr_width = ok_png_get_width_for_pass(decoder);
            curr_height = ok_png_get_height_for_pass(decoder);
            curr_bytes_per_scanline = (size_t)(1 + ((uint64_t)curr_width * bits_per_pixel + 7) / 8);
            if (curr_width == 0 || curr_height == 0) {
                // No data for this pass - happens if width or height <= 4
                decoder->ready_for_next_interlace_pass = true;
            } else {
                memset(decoder->curr_scanline, 0, curr_bytes_per_scanline);
                memset(decoder->prev_scanline, 0, curr_bytes_per_scanline);
                decoder->inflater_bytes_read = 0;
            }
        }

        // Read compressed data
        if (ok_inflater_needs_input(decoder->inflater)) {
            if (bytes_remaining == 0) {
                // Need more data, but there is no remaining data in this chunk.
                // There may be another IDAT chunk.
                return true;
            }
            const size_t len = min(inflate_buffer_size, bytes_remaining);
            if (!ok_read(decoder, decoder->inflate_buffer, len)) {
                return false;
            }
            bytes_remaining -= len;
            ok_inflater_set_input(decoder->inflater, decoder->inflate_buffer, len);
        }

        // Decompress data
        size_t len = ok_inflater_inflate(decoder->inflater,
                                         decoder->curr_scanline + decoder->inflater_bytes_read,
                                         curr_bytes_per_scanline - decoder->inflater_bytes_read);
        if (len == OK_SIZE_MAX) {
            ok_png_error(png, OK_PNG_ERROR_INFLATER, "Inflater error");
            return false;
        }
        decoder->inflater_bytes_read += len;
        if (decoder->inflater_bytes_read == curr_bytes_per_scanline) {
            // Apply filter
            const int filter = decoder->curr_scanline[0];
            if (filter > 0 && filter < OK_PNG_NUM_FILTERS) {
                ok_png_decode_filter(decoder->curr_scanline + 1, decoder->prev_scanline + 1,
                                     curr_bytes_per_scanline - 1, filter, bytes_per_pixel);
            } else if (filter != 0) {
                ok_png_error(png, OK_PNG_ERROR_INVALID, "Invalid filter type");
                return false;
            }

            // Transform
            ok_png_transform_scanline(decoder, decoder->curr_scanline + 1, curr_width);

            // Setup for next scanline or pass
            decoder->scanline++;
            if (decoder->scanline == curr_height) {
                decoder->ready_for_next_interlace_pass = true;
            } else {
                uint8_t *temp = decoder->curr_scanline;
                decoder->curr_scanline = decoder->prev_scanline;
                decoder->prev_scanline = temp;
                decoder->inflater_bytes_read = 0;
            }
        }
    }
}


static bool ok_png_read_transparency(ok_png_decoder *decoder, uint32_t chunk_length) {
    ok_png *png = decoder->png;
    png->has_alpha = true;

    if (decoder->color_type == OK_PNG_COLOR_TYPE_PALETTE) {
        if (chunk_length > decoder->palette_length || chunk_length > 256) {
            ok_png_error(png, OK_PNG_ERROR_INVALID,
                         "Invalid transparency length for palette color type");
            return false;
        }

        const bool should_premultiply = (decoder->decode_flags & OK_PNG_PREMULTIPLIED_ALPHA) != 0;
        uint8_t *dst = decoder->palette;
        uint8_t buffer[256];
        if (!ok_read(decoder, buffer, chunk_length)) {
            return false;
        }
        for (uint32_t i = 0; i < chunk_length; i++) {
            dst[3] = buffer[i];
            if (should_premultiply) {
                ok_png_premultiply(dst);
            }
            dst += 4;
        }
        return true;
    } else if (decoder->color_type == OK_PNG_COLOR_TYPE_GRAYSCALE) {
        if (chunk_length != 2) {
            ok_png_error(png, OK_PNG_ERROR_INVALID,
                         "Invalid transparency length for grayscale color type");
            return false;
        } else {
            uint8_t buffer[2];
            if (!ok_read(decoder, buffer, sizeof(buffer))) {
                return false;
            }
            const uint16_t v = readBE16(buffer);
            decoder->single_transparent_color_key[0] = v;
            decoder->single_transparent_color_key[1] = v;
            decoder->single_transparent_color_key[2] = v;
            decoder->has_single_transparent_color = true;
            return true;
        }
    } else if (decoder->color_type == OK_PNG_COLOR_TYPE_RGB) {
        if (chunk_length != 6) {
            ok_png_error(png, OK_PNG_ERROR_INVALID,
                         "Invalid transparency length for truecolor color type");
            return false;
        } else {
            uint8_t buffer[6];
            if (!ok_read(decoder, buffer, sizeof(buffer))) {
                return false;
            }
            decoder->single_transparent_color_key[0] = readBE16(buffer + 0);
            decoder->single_transparent_color_key[1] = readBE16(buffer + 2);
            decoder->single_transparent_color_key[2] = readBE16(buffer + 4);
            decoder->has_single_transparent_color = true;
            return true;
        }
    } else {
        ok_png_error(png, OK_PNG_ERROR_INVALID,
                     "Invalid transparency for color type");
        return false;
    }
}


static void ok_png_decode2(ok_png_decoder *decoder) {
    ok_png *png = decoder->png;

    uint8_t png_header[8];
    if (!ok_read(decoder, png_header, sizeof(png_header))) {
        return;
    }
    uint8_t png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
    if (memcmp(png_header, png_signature, 8) != 0) {
        ok_png_error(decoder->png, OK_PNG_ERROR_INVALID, "Invalid signature (not a PNG file)");
        return;
    }

    // When info_only is true, we only care about the IHDR chunk and whether or not
    // the tRNS chunk exists.
    bool info_only = (decoder->decode_flags & OK_PNG_INFO_ONLY) != 0;
    bool hdr_found = false;
    bool end_found = false;
    while (!end_found) {
        uint8_t chunk_header[8];
        uint8_t chunk_footer[4];
        if (!ok_read(decoder, chunk_header, sizeof(chunk_header))) {
            return;
        }
        const uint32_t chunk_length = readBE32(chunk_header);
        const uint32_t chunk_type = readBE32(chunk_header + 4);
        bool success = false;

        if (!hdr_found && chunk_type != OK_PNG_CHUNK_CGBI && chunk_type != OK_PNG_CHUNK_IHDR) {
            ok_png_error(png, OK_PNG_ERROR_INVALID, "IHDR chunk must appear first");
            return;
        }
        if (chunk_type == OK_PNG_CHUNK_IHDR) {
            if (hdr_found) {
                ok_png_error(png, OK_PNG_ERROR_INVALID, "Multiple IHDR chunks not allowed");
                return;
            }
            hdr_found = true;
            success = ok_png_read_header(decoder, chunk_length);
            if (success && info_only) {
                // If the png has alpha, then we have all the info we need.
                // Otherwise, continue scanning to see if the tRNS chunk exists.
                if (png->has_alpha) {
                    return;
                }
            }
        } else if (chunk_type == OK_PNG_CHUNK_CGBI) {
            success = ok_seek(decoder, (long)chunk_length);
            decoder->is_ios_format = true;
        } else if (chunk_type == OK_PNG_CHUNK_PLTE && !info_only) {
            success = ok_png_read_palette(decoder, chunk_length);
        } else if (chunk_type == OK_PNG_CHUNK_TRNS) {
            if (info_only) {
                // No need to parse this chunk, we have all the info we need.
                png->has_alpha = true;
                return;
            } else {
                success = ok_png_read_transparency(decoder, chunk_length);
            }
        } else if (chunk_type == OK_PNG_CHUNK_IDAT) {
            if (info_only) {
                // Both IHDR and tRNS must come before IDAT, so we have all the info we need.
                return;
            }
            success = ok_png_read_data(decoder, chunk_length);
        } else if (chunk_type == OK_PNG_CHUNK_IEND) {
            success = ok_seek(decoder, (long)chunk_length);
            end_found = true;
        } else {
            // Ignore this chunk
            success = ok_seek(decoder, (long)chunk_length);
        }

        if (!success) {
            return;
        }

        // Read the footer (CRC) and ignore it
        if (!ok_read(decoder, chunk_footer, sizeof(chunk_footer))) {
            return;
        }
    }

    // Sanity check
    if (!decoder->decoding_completed) {
        ok_png_error(png, OK_PNG_ERROR_INVALID, "Missing imaga data");
    }
}


void ok_png_decode(ok_png *png, ok_png_decode_flags decode_flags,
                   ok_png_input input, void *input_user_data,
                   ok_png_allocator allocator, void *allocator_user_data) {
    if (!input.read || !input.seek) {
        ok_png_error(png, OK_PNG_ERROR_API,
                     "Invalid argument: input read and seek functions must not be NULL");
        return;
    }

    if (!allocator.alloc || !allocator.free) {
        ok_png_error(png, OK_PNG_ERROR_API,
                     "Invalid argument: allocator alloc and free functions must not be NULL");
        return;
    }

    ok_png_decoder *decoder = allocator.alloc(allocator_user_data, sizeof(ok_png_decoder));
    if (!decoder) {
        ok_png_error(png, OK_PNG_ERROR_ALLOCATION, "Couldn't allocate decoder.");
        return;
    }
    memset(decoder, 0, sizeof(ok_png_decoder));

    decoder->png = png;
    decoder->decode_flags = decode_flags;
    decoder->input = input;
    decoder->input_user_data = input_user_data;
    decoder->allocator = allocator;
    decoder->allocator_user_data = allocator_user_data;

    ok_png_decode2(decoder);

    // Cleanup decoder
    ok_inflater_free(decoder->inflater);
    allocator.free(allocator_user_data, decoder->curr_scanline);
    allocator.free(allocator_user_data, decoder->prev_scanline);
    allocator.free(allocator_user_data, decoder->inflate_buffer);
    allocator.free(allocator_user_data, decoder->temp_data_row);
    allocator.free(allocator_user_data, decoder);
}


static bool ok_inflater_zlib_header(ok_inflater *inflater) {
    if (!ok_inflater_load_bits(inflater, 16)) {
        return false;
    } else {
        uint32_t compression_method = ok_inflater_read_bits(inflater, 4);
        uint32_t compression_info = ok_inflater_read_bits(inflater, 4);
        uint32_t flag_check = ok_inflater_read_bits(inflater, 5);
        uint32_t flag_dict = ok_inflater_read_bits(inflater, 1);
        uint32_t flag_compression_level = ok_inflater_read_bits(inflater, 2);

        uint32_t bits = ((compression_info << 12) | (compression_method << 8) |
                         (flag_compression_level << 6) | (flag_dict << 5) | flag_check);
        if (bits % 31 != 0) {
            ok_inflater_error(inflater, "Invalid zlib header");
            return false;
        }
        if (compression_method != 8) {
            ok_inflater_error(inflater, "Invalid inflater compression method");
            return false;
        }
        if (compression_info > 7) {
            ok_inflater_error(inflater, "Invalid window size");
            return false;
        }
        if (flag_dict) {
            ok_inflater_error(inflater, "Needs external dictionary");
            return false;
        }

        inflater->state = OK_INFLATER_STATE_READY_FOR_NEXT_BLOCK;
        return true;
    }
}


static bool ok_read(ok_png_decoder *decoder, uint8_t *buffer, size_t length) {
    if (decoder->input.read(decoder->input_user_data, buffer, length) == length) {
        return true;
    } else {
        ok_png_error(decoder->png, OK_PNG_ERROR_IO, "Read error: error calling input function.");
        return false;
    }
}


static int ok_inflater_decode_length(ok_inflater *inflater, int value) {
    if (value < 8) {
        return value + 3;
    } else {
        int len = OK_INFLATER_LENGTH_TABLE[value];
        unsigned int extra_bits = (unsigned int)((value >> 2) - 1);
        if (extra_bits <= 5) {
            if (!ok_inflater_load_bits(inflater, extra_bits)) {
                return -1;
            }
            len += ok_inflater_read_bits(inflater, extra_bits);
        }
        return len;
    }
}


ok_png ok_png_read_from_input(ok_png_decode_flags decode_flags,
                              ok_png_input input_callbacks, void *input_callbacks_user_data,
                              ok_png_allocator allocator, void *allocator_user_data) {
    ok_png png = { 0 };
    ok_png_decode(&png, decode_flags, input_callbacks, input_callbacks_user_data,
                  allocator, allocator_user_data);
    return png;
}


static bool ok_inflater_dynamic_block_code_lengths(ok_inflater *inflater) {
    while (inflater->state_count > 0) {
        if (!ok_inflater_load_bits(inflater, 3)) {
            return false;
        }
        int index = inflater->num_code_length_codes - inflater->state_count;
        inflater->tree_codes[OK_INFLATER_BIT_LENGTH_TABLE[index]] =
            (uint8_t)ok_inflater_read_bits(inflater, 3);
        inflater->state_count--;
    }
    ok_inflater_make_huffman_tree_from_array(inflater->code_length_huffman,
                                             inflater->tree_codes,
                                             OK_INFLATER_BIT_LENGTH_TABLE_LENGTH);

    inflater->state = OK_INFLATER_STATE_READING_DYNAMIC_LITERAL_TREE;
    inflater->huffman_code = -1;
    inflater->state_count = 0;
    return true;
}


size_t ok_inflater_inflate(ok_inflater *inflater, uint8_t *dst, size_t dst_len) {
    if (!inflater || inflater->state == OK_INFLATER_STATE_ERROR ||
        inflater->state == OK_INFLATER_STATE_DONE) {
        return OK_SIZE_MAX;
    }

    // Each state function should return false if input is needed or the buffer is full.
    // Run until one condition occurs:
    // 1. Output buffer can be filled,
    // 2. Internal buffer is full,
    // 3. Needs more input,
    // 4. Done inflating, or
    // 5. An error occured.
    while (ok_inflater_can_flush_total(inflater) < dst_len &&
           (*OK_INFLATER_STATE_FUNCTIONS[inflater->state])(inflater)) {
    }
    return ok_inflater_flush(inflater, dst, dst_len);
}


static inline uint32_t ok_inflater_read_bits(ok_inflater *inflater, unsigned int num_bits) {
    uint32_t ans = inflater->input_buffer & ((1 << num_bits) - 1);
    inflater->input_buffer >>= num_bits;
    inflater->input_buffer_bits -= num_bits;
    return ans;
}


static bool ok_inflater_inflate_huffman_tree(ok_inflater *inflater, ok_inflater_huffman_tree *tree,
                                             ok_inflater_huffman_tree *code_length_huffman,
                                             int num_codes) {
    if (num_codes < 0 || num_codes >= MAX_NUM_CODES) {
        ok_inflater_error(inflater, "Invalid num_codes");
        return false;
    }
    const uint16_t *tree_lookup_table = code_length_huffman->lookup_table;
    const unsigned int tree_bits = code_length_huffman->bits;
    // 0 - 15: Represent code lengths of 0 - 15
    //     16: Copy the previous code length 3 - 6 times.
    //         (2 bits of length)
    //     17: Repeat a code length of 0 for 3 - 10 times.
    //         (3 bits of length)
    //     18: Repeat a code length of 0 for 11 - 138 times
    //         (7 bits of length)
    while (inflater->state_count < num_codes) {
        if (inflater->huffman_code < 0) {
            inflater->huffman_code = ok_inflater_decode_literal(inflater, tree_lookup_table,
                                                                tree_bits);
            if (inflater->huffman_code < 0) {
                return false;
            }
        }
        if (inflater->huffman_code <= 15) {
            inflater->tree_codes[inflater->state_count++] = (uint8_t)inflater->huffman_code;
        } else {
            int value = 0;
            int len;
            unsigned int len_bits;
            switch (inflater->huffman_code) {
                case 16:
                    len = 3;
                    len_bits = 2;
                    if (inflater->state_count == 0) {
                        ok_inflater_error(inflater, "Invalid previous code");
                        return false;
                    }
                    value = inflater->tree_codes[inflater->state_count - 1];
                    break;
                case 17:
                    len = 3;
                    len_bits = 3;
                    break;
                case 18:
                    len = 11;
                    len_bits = 7;
                    break;
                default:
                    ok_inflater_error(inflater, "Invalid huffman code");
                    return false;
            }
            if (!ok_inflater_load_bits(inflater, len_bits)) {
                return false;
            }
            len += ok_inflater_read_bits(inflater, len_bits);
            if (len > num_codes - inflater->state_count) {
                ok_inflater_error(inflater, "Invalid length");
                return false;
            }
            memset(inflater->tree_codes + inflater->state_count, value, (size_t)len);
            inflater->state_count += len;
        }
        inflater->huffman_code = -1;
    }
    ok_inflater_make_huffman_tree_from_array(tree, inflater->tree_codes, num_codes);
    return true;
}


ok_png ok_png_read_with_allocator(FILE *file, ok_png_decode_flags decode_flags,
                                  ok_png_allocator allocator, void *allocator_user_data) {
    ok_png png = { 0 };
    if (file) {
        ok_png_decode(&png, decode_flags, OK_PNG_FILE_INPUT, file, allocator, allocator_user_data);
    } else {
        ok_png_error(&png, OK_PNG_ERROR_API, "File not found");
    }
    return png;
}


static inline uint16_t ok_inflater_can_write_total(const ok_inflater *inflater) {
    return inflater->buffer_start_pos - inflater->buffer_end_pos - 1;
}


static bool ok_inflater_init_fixed_huffman(ok_inflater *inflater) {
    if (!inflater->fixed_literal_huffman) {
        ok_inflater_huffman_tree *tree = ok_alloc(inflater, sizeof(ok_inflater_huffman_tree));
        if (tree) {
            uint8_t code_length[288];
            int i;
            for (i = 0; i < 144; i++) {
                code_length[i] = 8;
            }
            for (i = 144; i < 256; i++) {
                code_length[i] = 9;
            }
            for (i = 256; i < 280; i++) {
                code_length[i] = 7;
            }
            for (i = 280; i < 288; i++) {
                code_length[i] = 8;
            }
            ok_inflater_make_huffman_tree_from_array(tree, code_length,
                                                     sizeof(code_length) / sizeof(code_length[0]));
            inflater->fixed_literal_huffman = tree;
        }
    }
    if (!inflater->fixed_distance_huffman) {
        ok_inflater_huffman_tree *tree = ok_alloc(inflater, sizeof(ok_inflater_huffman_tree));
        if (tree) {
            uint8_t distance_code_length[32];
            for (int i = 0; i < 32; i++) {
                distance_code_length[i] = 5;
            }
            ok_inflater_make_huffman_tree_from_array(tree, distance_code_length, 32);
            inflater->fixed_distance_huffman = tree;
        }
    }
    return inflater->fixed_literal_huffman && inflater->fixed_distance_huffman;
}


bool ok_inflater_needs_input(const ok_inflater *inflater) {
    return inflater &&
        inflater->state != OK_INFLATER_STATE_ERROR &&
        ok_inflater_can_flush_total(inflater) == 0 &&
        inflater->input == inflater->input_end;
}


static inline bool ok_inflater_can_read_bits(ok_inflater *inflater, unsigned int num_bits) {
    if (ok_inflater_load_bits(inflater, num_bits)) {
        return true;
    }
    int bytes_needed = (num_bits - inflater->input_buffer_bits + 7) >> 3;
    return bytes_needed <= (inflater->input_end - inflater->input);
}


void ok_inflater_set_input(ok_inflater *inflater, const uint8_t *buffer, size_t buffer_length) {
    if (inflater) {
        if (inflater->input == inflater->input_end) {
            inflater->input = buffer;
            inflater->input_end = inflater->input + buffer_length;
        } else {
            ok_inflater_error(inflater, "ok_inflater_set_input was called with unread input data.");
        }
    }
}


ok_png ok_png_read(FILE *file, ok_png_decode_flags decode_flags) {
    return ok_png_read_with_allocator(file, decode_flags, OK_PNG_DEFAULT_ALLOCATOR, NULL);
}


void ok_inflater_free(ok_inflater *inflater) {
    if (inflater) {
        ok_png_allocator allocator = inflater->allocator;
        void *allocator_user_data = inflater->allocator_user_data;
        allocator.free(allocator_user_data, inflater->buffer);
        allocator.free(allocator_user_data, inflater->code_length_huffman);
        allocator.free(allocator_user_data, inflater->literal_huffman);
        allocator.free(allocator_user_data, inflater->distance_huffman);
        allocator.free(allocator_user_data, inflater->fixed_literal_huffman);
        allocator.free(allocator_user_data, inflater->fixed_distance_huffman);
        allocator.free(allocator_user_data, inflater);
    }
}


static bool ok_inflater_distance_tree(ok_inflater *inflater) {
    bool done = ok_inflater_inflate_huffman_tree(inflater, inflater->distance_huffman,
                                                 inflater->code_length_huffman,
                                                 inflater->num_distance_codes);
    if (done) {
        inflater->state = OK_INFLATER_STATE_READING_DYNAMIC_COMPRESSED_BLOCK;
        inflater->huffman_code = -1;
        return true;
    } else {
        return false;
    }
}