/**
 * LR parser for C++ generated by the Syntax tool.
 *
 * https://www.npmjs.com/package/syntax-cli
 *
 *   npm install -g syntax-cli
 *
 *   syntax-cli --help
 *
 * To regenerate run:
 *
 *   syntax-cli \
 *     --grammar ~/path-to-grammar-file \
 *     --mode <parsing-mode> \
 *     --output ~/ParserClassName.h
 */
#ifndef __Syntax_LR_Parser_h
#define __Syntax_LR_Parser_h

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field"

#include <assert.h>
#include <array>
#include <iostream>
#include <map>
#include <memory>
#include <regex>
#include <sstream>
#include <string>
#include <vector>

// ------------------------------------
// Module include prologue.
//
// Should include at least value/result type:
//
// type Value = <...>;
//
// Or struct Value { ... };
//
// Can also include parsing hooks:
//
//   void onParseBegin(const Parser& parser, const std::string& str) {
//     ...
//   }
//
//   void onParseBegin(const Parser& parser, const Value& result) {
//     ...
//   }
//
// clang-format off
{{{MODULE_INCLUDE}}}  // clang-format on

namespace syntax {

/**
 * Tokenizer class.
 */
// clang-format off
{{{TOKENIZER}}}
// clang-format on

#define POP_V()              \
  parser.valuesStack.back(); \
  parser.valuesStack.pop_back()

#define POP_T()              \
  parser.tokensStack.back(); \
  parser.tokensStack.pop_back()

#define PUSH_VR() parser.valuesStack.push_back(__)
#define PUSH_TR() parser.tokensStack.push_back(__)

/**
 * Parsing table type.
 */
enum class TE {
  Accept,
  Shift,
  Reduce,
  Transit,
};

/**
 * Parsing table entry.
 */
struct TableEntry {
  TE type;
  int value;
};

// clang-format off
class {{{PARSER_CLASS_NAME}}};
// clang-format on

using yyparse = {{{PARSER_CLASS_NAME}}};

typedef void (*ProductionHandler)(yyparse&);

/**
 * Encoded production.
 *
 * opcode - encoded index
 * rhsLength - length of the RHS to pop.
 */
struct Production {
  int opcode;
  int rhsLength;
  ProductionHandler handler;
};

// Key: Encoded symbol (terminal or non-terminal) index
// Value: TableEntry
using Row = std::map<int, TableEntry>;

/**
 * Parser class.
 */
// clang-format off
class {{{PARSER_CLASS_NAME}}} {
  // clang-format on
 public:
  /**
   * Parsing values stack.
   */
  std::vector<Value> valuesStack;

  /**
   * Token values stack.
   */
  std::vector<std::string> tokensStack;

  /**
   * Parsing states stack.
   */
  std::vector<int> statesStack;

  /**
   * Tokenizer.
   */
  Tokenizer tokenizer;

  /**
   * Previous state to calculate the next one.
   */
  int previousState;

  /**
   * Parses a string.
   */
  Value parse(const std::string& str) {
    // clang-format off
    {{{ON_PARSE_BEGIN_CALL}}}
    // clang-format on

    // Initialize the tokenizer and the string.
    tokenizer.initString(str);

    // Initialize the stacks.
    valuesStack.clear();
    tokensStack.clear();
    statesStack.clear();

    // Initial 0 state.
    statesStack.push_back(0);

    auto token = tokenizer.getNextToken();
    auto shiftedToken = token;

    // Main parsing loop.
    for (;;) {
      auto state = statesStack.back();
      auto column = (int)token->type;

      if (table_[state].count(column) == 0) {
        throwUnexpectedToken(token);
      }

      auto entry = table_[state].at(column);

      // Shift a token, go to state.
      if (entry.type == TE::Shift) {
        // Push token.
        tokensStack.push_back(token->value);

        // Push next state number: "s5" -> 5
        statesStack.push_back(entry.value);

        shiftedToken = token;
        token = tokenizer.getNextToken();
      }

      // Reduce by production.
      else if (entry.type == TE::Reduce) {
        auto productionNumber = entry.value;
        auto production = productions_[productionNumber];

        tokenizer.yytext = shiftedToken->value;

        auto rhsLength = production.rhsLength;
        while (rhsLength > 0) {
          statesStack.pop_back();
          rhsLength--;
        }

        // Call the handler.
        production.handler(*this);

        auto previousState = statesStack.back();

        auto symbolToReduceWith = production.opcode;
        auto nextStateEntry = table_[previousState].at(symbolToReduceWith);
        assert(nextStateEntry.type == TE::Transit);

        statesStack.push_back(nextStateEntry.value);
      }

      // Accept the string.
      else if (entry.type == TE::Accept) {
        // Pop state number.
        statesStack.pop_back();

        // Pop the parsed value.
        // clang-format off
        {{{PARSED_RESULT}}}
        // clang-format on

        if (statesStack.size() != 1 || statesStack.back() != 0 ||
            tokenizer.hasMoreTokens()) {
          throwUnexpectedToken(token);
        }

        statesStack.pop_back();

        // clang-format off
        {{{ON_PARSE_END_CALL}}}
        // clang-format on

        return result;
      }
    }
  }

 private:
  /**
   * Throws parser error on unexpected token.
   */
  [[noreturn]] void throwUnexpectedToken(SharedToken token) {
    if (token->type == TokenType::__EOF && !tokenizer.hasMoreTokens()) {
      std::string errMsg = "Unexpected end of input.\n";
      std::cerr << errMsg;
      throw std::runtime_error(errMsg.c_str());
    }
    tokenizer.throwUnexpectedToken(token->value, token->startLine,
                                   token->startColumn);
  }

  // clang-format off
  static constexpr size_t PRODUCTIONS_COUNT = {{{PRODUCTIONS_COUNT}}};
  static std::array<Production, PRODUCTIONS_COUNT> productions_;

  static constexpr size_t ROWS_COUNT = {{{ROWS_COUNT}}};
  static std::array<Row, ROWS_COUNT> table_;
  // clang-format on
};

// ------------------------------------------------------------------
// Productions.

// clang-format off
{{{PRODUCTION_HANDLERS}}}
// clang-format on

// clang-format off
std::array<Production, yyparse::PRODUCTIONS_COUNT> yyparse::productions_ = {{{PRODUCTIONS}}};
// clang-format on

// ------------------------------------------------------------------
// Parsing table.

// clang-format off
std::array<Row, yyparse::ROWS_COUNT> yyparse::table_ = {{{TABLE}}};
// clang-format on

}  // namespace syntax

#endif