'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * The MIT License (MIT)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      * Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      */

var _grammarSymbol = require('../grammar/grammar-symbol');

var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);

var _llParsingTable = require('./ll-parsing-table');

var _llParsingTable2 = _interopRequireDefault(_llParsingTable);

var _llParserGeneratorDefault = require('./ll-parser-generator-default');

var _llParserGeneratorDefault2 = _interopRequireDefault(_llParserGeneratorDefault);

var _tokenizer = require('../tokenizer');

var _tokenizer2 = _interopRequireDefault(_tokenizer);

var _specialSymbols = require('../special-symbols');

var _debug = require('../debug');

var _debug2 = _interopRequireDefault(_debug);

var _os = require('os');

var _os2 = _interopRequireDefault(_os);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/**
 * Implements LL(1) parsing algorithm.
 */
var LLParser = function () {
  function LLParser(_ref) {
    var grammar = _ref.grammar,
        parserModule = _ref.parserModule;

    _classCallCheck(this, LLParser);

    this._grammar = grammar;
    this._parserModule = parserModule;

    this._table = new _llParsingTable2.default({
      grammar: grammar
    });

    // If there are conflicts, parsing is not possible.
    this._validateConflicts();

    this._tokenizer = new _tokenizer2.default({
      lexGrammar: grammar.getLexGrammar()
    });

    // Parsing stack.
    this._stack = [];

    // Stores production numbers used at parsing.
    this._productionNumbers = [];
  }

  /**
   * Returns production numbers used to parse a string.
   */


  _createClass(LLParser, [{
    key: 'getProductionNumbers',
    value: function getProductionNumbers() {
      return this._productionNumbers;
    }
  }, {
    key: 'parse',
    value: function parse(string) {
      // If parser module has been generated, use it.
      if (this._parserModule) {
        _debug2.default.time('LL parsing from module');

        var value = this._parserModule.parse(string);

        _debug2.default.timeEnd('LL parsing from module');

        return {
          status: 'accept',
          value: value
        };
      }

      _debug2.default.time('LL parsing');

      this._tokenizer.initString(string);

      // Initialize the stack with the `$` at the bottom, and the start symbol.
      this._stack = [_grammarSymbol2.default.get(_specialSymbols.EOF), _grammarSymbol2.default.get(this._grammar.getStartSymbol())];

      this._productionNumbers = [];

      var token = this._tokenizer.getNextToken();
      var top = null;

      do {
        top = this._stack.pop();

        // Terminal is on the stack, just advance.
        if (this._grammar.isTokenSymbol(top) && top.getSymbol() === token.type) {
          // We already popped the symbol from the stack,
          // so just advance the cursor.
          token = this._tokenizer.getNextToken();
          continue;
        }

        // Else, it's a non-terminal, do derivation (replace it
        // in the stack with corresponding production).
        this._doDerivation(top, token);
      } while (this._tokenizer.hasMoreTokens() || this._stack.length > 1);

      // If the string reached EOF, and we still have non-terminal symbols
      // on the stack, we need to clean them up, they have to derive ε.
      while (this._stack.length !== 1) {
        this._doDerivation(this._stack.pop(), token);
      }

      // At the end the stack should contain only `$`,
      // as well as the last token should be the `$` marker.
      if (!this._stack[0].isEOF() || token.type !== _specialSymbols.EOF) {
        this._parseError('stack is not empty: ' + this._stack.map(function (s) {
          return s.getSymbol();
        }) + (', ' + token.value));
      }

      _debug2.default.timeEnd('LL parsing');

      return {
        status: 'accept',
        semanticValue: true
      };
    }
  }, {
    key: '_doDerivation',
    value: function _doDerivation(top, token) {
      var derivedRHS = this._getDerivedRHS(top, token);

      // If we have production like F -> ε, we should just pop
      // the symbol, and don't push its derivation (the ε).
      if (!derivedRHS[0].isEpsilon()) {
        var _stack;

        (_stack = this._stack).push.apply(_stack, _toConsumableArray(derivedRHS));
      }
    }
  }, {
    key: '_getDerivedRHS',
    value: function _getDerivedRHS(top, token) {
      var nextProductionNumber = this._table.get()[top.getSymbol()][token.type];

      if (!nextProductionNumber) {
        this._unexpectedToken(token);
      }

      if (this._table.entryHasConflict(nextProductionNumber)) {
        this._parseError('Found conflict in state ' + top.getSymbol() + ':' + token.type + '. ' + ('Predicted productions: ' + nextProductionNumber));
      }

      var nextProduction = this._grammar.getProduction(nextProductionNumber);

      this._productionNumbers.push(nextProductionNumber);

      // We should return reversed RHS in order to push on the stack.
      return nextProduction.getRHS().slice().reverse();
    }
  }, {
    key: '_validateConflicts',
    value: function _validateConflicts() {
      if (!this._table.hasConflicts()) {
        return;
      }

      var messages = [''];
      var conflicts = this._table.getConflicts();

      for (var nonTerminal in conflicts) {
        var conflictMessage = nonTerminal + ': ';
        var row = conflicts[nonTerminal];

        var rowMessages = [];
        for (var terminal in row) {
          rowMessages.push(terminal + ' -- ' + row[terminal]);
        }

        conflictMessage += rowMessages.join(', ');
        messages.push(conflictMessage);
      }

      this._parseError('Grammar has conflicts:\n' + messages.join('\n- '));
    }
  }, {
    key: '_unexpectedEndOfInput',
    value: function _unexpectedEndOfInput() {
      this._parseError('Unexpected end of input.');
    }
  }, {
    key: '_unexpectedToken',
    value: function _unexpectedToken(token) {
      if (token.type === _specialSymbols.EOF) {
        this._unexpectedEndOfInput();
      }

      this._tokenizer.throwUnexpectedToken(token.value, token.startLine, token.startColumn);
    }
  }, {
    key: '_parseError',
    value: function _parseError(message) {
      throw new SyntaxError(message);
    }
  }], [{
    key: 'fromParserGenerator',
    value: function fromParserGenerator(_ref2) {
      var grammar = _ref2.grammar;

      // Generate parser in the temp directory.
      var outputFile = _path2.default.resolve(_os2.default.tmpdir(), '.syntax-parser.js');

      var parserModule = new _llParserGeneratorDefault2.default({
        grammar: grammar,
        outputFile: outputFile
      }).generate();

      return new LLParser({ grammar: grammar, parserModule: parserModule });
    }
  }]);

  return LLParser;
}();

exports.default = LLParser;