// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "recognizer.h"

#include <QtCore/qdir.h>

#include <cstdlib>
#include <cstring>
#include <cctype>

using namespace Qt::StringLiterals;

Recognizer::Recognizer (Grammar *grammar, bool no_lines):
  tos(0),
  stack_size(0),
  state_stack(nullptr),
  _M_line(1),
  _M_action_line(0),
  _M_grammar(grammar),
  _M_no_lines(no_lines)
{
}

Recognizer::~Recognizer()
{
  if (stack_size)
    ::free(state_stack);
}

inline void Recognizer::reallocateStack()
{
  if (! stack_size)
    stack_size = 128;
  else
    stack_size <<= 1;

  sym_stack.resize (stack_size);

  if (! state_stack)
    state_stack = reinterpret_cast<int*> (::malloc(stack_size * sizeof(int)));
  else
    state_stack = reinterpret_cast<int*> (::realloc(state_stack, stack_size * sizeof(int)));
}

int Recognizer::nextToken()
{
  QString text;

 Lagain:
  while (ch.isSpace ())
    inp ();

  if (ch.isNull ())
    return EOF_SYMBOL;

  int token = ch.unicode ();

  if (token == '"')
    {
      inp(); // skip "
      text.clear ();
      while (!ch.isNull () && ch != u'"')
        {
          if (ch == u'\\')
            {
              text += ch;
              inp();
            }
          text += ch;
          inp ();
        }

      if (ch == u'"')
        inp ();
      else
        qerr() << _M_input_file << ":" << _M_line << ": Warning. Expected `\"'" << Qt::endl;

      _M_current_value = text;
      return (token = STRING_LITERAL);
    }

  else if (ch.isLetterOrNumber () || ch == u'_')
    {
      text.clear ();
      do { text += ch; inp (); }
      while (ch.isLetterOrNumber () || ch == u'_' || ch == u'.');
      _M_current_value = text;
      return (token = ID);
    }

  else if (token == '%')
    {
      text.clear ();

      do { inp (); }
      while (ch.isSpace ());

      do { text += ch; inp (); }
      while (ch.isLetterOrNumber () || ch == u'_' || ch == u'-');

      if (text == "token_prefix"_L1)
        return (token = TOKEN_PREFIX);
      else if (text == "merged_output"_L1)
        return (token = MERGED_OUTPUT);
      else if (text == "token"_L1)
        return (token = TOKEN);
      else if (text == "start"_L1)
        return (token = START);
      else if (text == "parser"_L1)
        return (token = PARSER);
      else if (text == "decl"_L1)
        return (token = DECL_FILE);
      else if (text == "impl"_L1)
        return (token = IMPL_FILE);
      else if (text == "expect"_L1)
        return (token = EXPECT);
      else if (text == "expect-rr"_L1)
        return (token = EXPECT_RR);
      else if (text == "left"_L1)
        return (token = LEFT);
      else if (text == "right"_L1)
        return (token = RIGHT);
      else if (text == "nonassoc"_L1)
        return (token = NONASSOC);
      else if (text == "prec"_L1)
        return (token = PREC);
      else
        {
          qerr() << _M_input_file << ":" << _M_line << ": Unknown keyword `" << text << "'" << Qt::endl;
          exit (EXIT_FAILURE);
          return (token = ERROR);
        }
    }

  inp ();

  if (token == '-' && ch == u'-')
    {
      do { inp (); }
      while (!ch.isNull () && ch != u'\n');
      goto Lagain;
    }

  else if (token == ':' && ch == u':')
    {
      inp ();
      if (ch != u'=')
        return (token = ERROR);
      inp ();
      return (token = COLON);
    }

  else if (token == '/' && ch == u':')
    {
      _M_action_line = _M_line;

      text.clear ();
      if (! _M_no_lines)
        text += "\n#line "_L1 + QString::number(_M_action_line) +
                " \""_L1 + QDir::fromNativeSeparators(_M_input_file) + "\"\n"_L1;
      inp (); // skip ':'

      forever
        {
          while (! ch.isNull ())
            {
              token = ch.unicode ();
              inp ();

              if (token == ':' && ch == u'/')
                break;

              text += QLatin1Char (token);
            }

          if (ch != u'/')
            return (token = ERROR);

          inp ();

          if (ch.isNull () || ch.isSpace ())
            {
              _M_current_value = text;
              return (token = DECL);
            }
          else
            text += ":/"_L1;
        }
    }

  else if (token == '/' && ch == u'.')
    {
      _M_action_line = _M_line;

      text.clear ();
      if (! _M_no_lines)
        text += "\n#line "_L1 + QString::number(_M_action_line) +
                " \""_L1 + QDir::fromNativeSeparators(_M_input_file) + "\"\n"_L1;

      inp (); // skip ':'

      forever
        {
          while (! ch.isNull ())
            {
              token = ch.unicode ();
              inp ();

              if (token == '.' && ch == u'/')
                break;

              text += QLatin1Char (token);
            }

          if (ch != u'/')
            return (token = ERROR);

          inp ();

          if (ch.isNull () || ch.isSpace ())
            {
              _M_current_value = text;
              return (token = IMPL);
            }
          else
            text += ""_L1;
        }
    }

  switch (token) {
  case ':':
    return (token = COLON);

  case ';':
    return (token = SEMICOLON);

  case '|':
    return (token = OR);

  default:
    break;
  }

  return token;
}

bool Recognizer::parse (const QString &input_file)
{
  _M_input_file = input_file;

  QFile file(_M_input_file);
  if (! file.open(QFile::ReadOnly))
    {
      qerr() << "qlalr: no input file\n";
      return false;
    }

  QString _M_contents = QTextStream(&file).readAll();
  _M_firstChar = _M_contents.constBegin();
  _M_lastChar = _M_contents.constEnd();
  _M_currentChar = _M_firstChar;
  _M_line = 1;

  int yytoken = -1;
  inp ();

  reallocateStack();

  _M_current_rule = _M_grammar->rules.end ();
  _M_decls.clear ();
  _M_impls.clear ();

  tos = 0;
  state_stack[++tos] = 0;

  while (true)
    {
      if (yytoken == -1 && - TERMINAL_COUNT != action_index [state_stack [tos]])
        yytoken = nextToken();

      int act = t_action (state_stack [tos], yytoken);

      if (act == ACCEPT_STATE)
        return true;

      else if (act > 0)
        {
          if (++tos == stack_size)
            reallocateStack();

          sym_stack [tos] = _M_current_value;
          state_stack [tos] = act;
          yytoken = -1;
        }

      else if (act < 0)
        {
          int r = - act - 1;

          tos -= rhs [r];
          act = state_stack [tos++];

          switch (r) {

case 3: {
  Name name = _M_grammar->intern (sym(2));
  _M_grammar->start = name;
  _M_grammar->non_terminals.insert (name);
} break;

case 5: {
  _M_grammar->table_name = sym(2);
} break;

case 6: {
  _M_grammar->merged_output = sym(2);
} break;

case 7: {
   _M_grammar->decl_file_name = sym(2);
} break;

case 8: {
   _M_grammar->impl_file_name = sym(2);
} break;

case 9: {
   _M_grammar->expected_shift_reduce = sym(2).toInt();
} break;

case 10: {
   _M_grammar->expected_reduce_reduce = sym(2).toInt();
} break;

case 11: {
  _M_grammar->token_prefix = sym(2);
} break;
case 17:case 18: {
  Name name = _M_grammar->intern (sym(1));
  _M_grammar->terminals.insert (name);
  _M_grammar->spells.insert (name, sym(2));
} break;

case 19: {
  _M_grammar->current_assoc = Grammar::Left;
  ++_M_grammar->current_prec;
} break;

case 20: {
  _M_grammar->current_assoc = Grammar::Right;
  ++_M_grammar->current_prec;
} break;

case 21: {
  _M_grammar->current_assoc = Grammar::NonAssoc;
  ++_M_grammar->current_prec;
} break;

case 25: {
  Name name = _M_grammar->intern (sym(1));
  _M_grammar->terminals.insert (name);

  Grammar::TokenInfo info;
  info.prec = _M_grammar->current_prec;
  info.assoc = _M_grammar->current_assoc;
  _M_grammar->token_info.insert (name, info);
} break;

case 26: {
  _M_decls += expand (sym(1));
} break;

case 27: {
  _M_impls += expand (sym(1));
} break;

case 34: {
  _M_current_rule = _M_grammar->rules.insert (_M_grammar->rules.end (), Rule ());
  _M_current_rule->lhs = _M_grammar->intern (sym(1));
  _M_grammar->declared_lhs.insert (_M_current_rule->lhs);

  if (_M_grammar->terminals.find (_M_current_rule->lhs) != _M_grammar->terminals.end ())
    {
      qerr() << _M_input_file << ":" << _M_line << ": Invalid non terminal `" << *_M_current_rule->lhs << "'" << Qt::endl;
      return false;
    }

  _M_grammar->non_terminals.insert (_M_current_rule->lhs);
} break;

case 38: {
  Name lhs = _M_current_rule->lhs;
  _M_current_rule = _M_grammar->rules.insert (_M_grammar->rules.end (), Rule ());
  _M_current_rule->lhs = lhs;
  _M_grammar->declared_lhs.insert (_M_current_rule->lhs);

  if (_M_grammar->terminals.find (_M_current_rule->lhs) != _M_grammar->terminals.end ())
    {
      qerr() << _M_input_file << ":" << _M_line << ": Invalid non terminal `" << *_M_current_rule->lhs << "'" << Qt::endl;
      return false;
    }

  _M_grammar->non_terminals.insert (_M_current_rule->lhs);
} break;

case 39: {
  _M_current_rule->prec = _M_grammar->names.end ();

  for (NameList::iterator it = _M_current_rule->rhs.begin (); it != _M_current_rule->rhs.end (); ++it)
    {
      if (! _M_grammar->isTerminal (*it))
        continue;

      _M_current_rule->prec = *it;
    }
} break;

case 40: {
  Name tok = _M_grammar->intern (sym(2));
  if (! _M_grammar->isTerminal (tok))
    {
      qerr() << _M_input_file << ":" << _M_line << ": `" << *tok << " is not a terminal symbol" << Qt::endl;
      _M_current_rule->prec = _M_grammar->names.end ();
    }
  else
    _M_current_rule->prec = tok;
} break;

case 42: {
  Name name = _M_grammar->intern (sym(2));

  if (_M_grammar->terminals.find (name) == _M_grammar->terminals.end ())
    _M_grammar->non_terminals.insert (name);

  _M_current_rule->rhs.push_back (name);
} break;

case 43: {
  sym(1) = QString();
} break;

          } // switch

          state_stack [tos] = nt_action (act, lhs [r] - TERMINAL_COUNT);
        }

      else
        {
          break;
        }
    }

  qerr() << _M_input_file << ":" << _M_line << ": Syntax error" << Qt::endl;
  return false;
}

