'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 _setsGenerator = require('../sets-generator');

var _setsGenerator2 = _interopRequireDefault(_setsGenerator);

var _tablePrinter = require('../table-printer');

var _tablePrinter2 = _interopRequireDefault(_tablePrinter);

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

var _colors = require('colors');

var _colors2 = _interopRequireDefault(_colors);

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

var _debug2 = _interopRequireDefault(_debug);

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

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

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

/**
 * LL parsing table.
 *
 * Example for a left-factored calculator grammar:
 *
 *   1. E -> T E'
 *
 *   2. E' -> "+" T E'
 *   3.     | ε
 *
 *   4. T -> F T'
 *
 *   5. T' -> "*" F T'
 *   6.     | ε
 *
 *   7. F -> "id"
 *   8.    | "(" E ")"
 *
 * LL(1) parsing table:
 *
 * ┌────┬─────┬─────┬──────┬─────┬─────┬───┐
 * │    │ "+" │ "*" │ "id" │ "(" │ ")" │ $ │
 * ├────┼─────┼─────┼──────┼─────┼─────┼───┤
 * │ E  │ -   │ -   │ 1    │ 1   │ -   │ - │
 * ├────┼─────┼─────┼──────┼─────┼─────┼───┤
 * │ E' │ 2   │ -   │ -    │ -   │ 3   │ 3 │
 * ├────┼─────┼─────┼──────┼─────┼─────┼───┤
 * │ T  │ -   │ -   │ 4    │ 4   │ -   │ - │
 * ├────┼─────┼─────┼──────┼─────┼─────┼───┤
 * │ T' │ 6   │ 5   │ -    │ -   │ 6   │ 6 │
 * ├────┼─────┼─────┼──────┼─────┼─────┼───┤
 * │ F  │ -   │ -   │ 7    │ 8   │ -   │ - │
 * └────┴─────┴─────┴──────┴─────┴─────┴───┘
 *
 * Notes:
 *
 *   - Row headers are grammar non-terminals
 *
 *   - Columns are the grammar tokens
 *
 *   - The entries are the next production number to apply
 *     for derivation (replacing a non-terminal on the stack with
 *     its right-hand side).
 *
 *   - The entries are build from "predict-sets" (combination of the
 *     "first", and "follow" sets).
 */
var LLParsingTable = function () {
  /**
   * Builds an LL parsing table for a given grammar.
   */
  function LLParsingTable(_ref) {
    var grammar = _ref.grammar;

    _classCallCheck(this, LLParsingTable);

    this._grammar = grammar;
    this._setsGenerator = new _setsGenerator2.default({ grammar: grammar });

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

    this._tableTokens = grammar.getTerminals().concat(grammar.getTokens(), _grammarSymbol2.default.get(_specialSymbols.EOF));

    this._table = this._build();

    _debug2.default.timeEnd('Building LL parsing table');
  }

  _createClass(LLParsingTable, [{
    key: 'get',
    value: function get() {
      return this._table;
    }
  }, {
    key: 'print',
    value: function print() {
      this._grammar.print();

      console.info('\n' + this._grammar.getMode().toString() + ' parsing table:\n');

      var tokenSymbols = this._tableTokens.map(function (token) {
        return token.getSymbol();
      });

      var printer = new _tablePrinter2.default({
        head: [''].concat(tokenSymbols)
      });

      for (var nonTerminal in this._table) {
        var stateLabel = _colors2.default.blue(nonTerminal);
        var row = _defineProperty({}, stateLabel, []);
        for (var k = 0; k < tokenSymbols.length; k++) {
          var entry = this._table[nonTerminal][tokenSymbols[k]] || '';

          if (this.entryHasConflict(entry)) {
            entry = _colors2.default.red(entry);
          }

          row[stateLabel].push(entry);
        }
        printer.push(row);
      }

      console.info(printer.toString());
      console.info('');
    }

    /**
     * Whether the table/grammar has conflicts.
     */

  }, {
    key: 'hasConflicts',
    value: function hasConflicts() {
      return this._hasConflicts;
    }

    /**
     * Returns table/grammar conflicts.
     */

  }, {
    key: 'getConflicts',
    value: function getConflicts() {
      if (!this._conflicts) {
        this._conflicts = this._analyzeConfilcts();
        this._hasConflicts = Object.keys(this._conflicts).length !== 0;
      }
      return this._conflicts;
    }
  }, {
    key: '_analyzeConfilcts',
    value: function _analyzeConfilcts() {
      var conflicts = Object.create(null);

      for (var nonTerminal in this._table) {
        var row = this._table[nonTerminal];

        for (var token in row) {
          var entry = row[token];

          if (!this.entryHasConflict(entry)) {
            continue;
          }

          if (!conflicts[nonTerminal]) {
            conflicts[nonTerminal] = {};
          }

          conflicts[nonTerminal][token] = entry;
        }
      }

      return conflicts;
    }

    /**
     * Builds the LL parsing table from First and Follow sets.
     *
     * To build an LL(1) parsing table we need the Predict set,
     * however the Predict set is just a combination of the
     * First set of the production, plus the Follow set if the
     * production derives epsilon. So in building the table
     * we use First and Follow sets directly delegating to needed
     * parts during the table construction.
     */

  }, {
    key: '_build',
    value: function _build() {
      var table = {};

      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = this._grammar.getProductions()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var production = _step.value;

          var lhs = production.getLHS();
          var rhs = production.getRHS();
          var lhsSymbol = lhs.getSymbol();

          // Initialize columns for this non-terminal.
          if (!table[lhsSymbol]) {
            table[lhsSymbol] = {};
          }

          // All productions goes under the terminal column, if
          // this terminal is not epsilon. Otherwise, an ε-production
          // goes under the columns from the Follow set of LHS.

          var set = !production.isEpsilon() ? this._setsGenerator.firstOfRHS(rhs) : this._setsGenerator.followOf(lhs);

          for (var terminal in set) {
            this._putProductionNumber(table[lhsSymbol], terminal, production.getNumber());
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      return table;
    }
  }, {
    key: 'entryHasConflict',
    value: function entryHasConflict(entry) {
      return entry.includes('/');
    }

    /**
     * If we can any conflict ("FIRST/FIRST", "FIRST/FOLLOW", "FOLLOW/FOLLOW"),
     * the table entry records via `/`, e.g. "2/5" - conflict, ambiguous choice
     * of the next grammar rule.
     */

  }, {
    key: '_putProductionNumber',
    value: function _putProductionNumber(row, column, entry) {
      var previousEntry = row[column];

      if (previousEntry === entry) {
        return;
      }

      // Exclude duplicates for possibly the same conflict entry.
      if (previousEntry) {
        previousEntry = previousEntry.split('/');
        if (!previousEntry.includes(entry)) {
          previousEntry.push(entry);
        }
        entry = previousEntry.join('/');
      }

      row[column] = entry.toString();
    }
  }]);

  return LLParsingTable;
}();

exports.default = LLParsingTable;