'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 _grammar = require('../grammar/grammar');

var _grammar2 = _interopRequireDefault(_grammar);

var _grammarMode = require('../grammar/grammar-mode');

var _lrItem = require('./lr-item');

var _lrItem2 = _interopRequireDefault(_lrItem);

var _setsGenerator = require('../sets-generator');

var _setsGenerator2 = _interopRequireDefault(_setsGenerator);

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

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

var _debug2 = _interopRequireDefault(_debug);

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 _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"); } }

/**
 * Canonical collection of LR items.
 *
 * LR(0) and SLR(1) use collection of LR(0)-items,
 * LALR(1) and CLR(1) use collection of LR(1)-items.
 *
 * The collection is built for a grammar starting from the root
 * item (for the augmented production), which recursively
 * applies "closure" and "goto" operations.
 */
var CanonicalCollection = function () {
  function CanonicalCollection(_ref) {
    var grammar = _ref.grammar;

    _classCallCheck(this, CanonicalCollection);

    this._grammar = grammar;

    // Stores transition from a kernel set to an outer state.
    // This is to reuse the same outer state, if the transition was
    // already calculated. In this case we just connect to the outer state.
    this._kernelSetsTransitions = {};

    // Stores states by their kernel items LR(0) key. This is to merge
    // similar states in case of LALR(1) mode.
    this._lr0ItemSets = {};

    // All the states that form this collection.
    this._states = new Set();

    _debug2.default.time('Building canonical collection');

    // Root item for the augmented production, "closure" and "goto"
    // operations applied on this item build the entire collection.
    this._rootItem = new _lrItem2.default(
    /* production */this._grammar.getAugmentedProduction(),
    /* dotPosition */0,
    /* grammar */this._grammar,
    /* canonicalCollection */this,
    /* setsGenerator */new _setsGenerator2.default({ grammar: grammar }),
    /*lookaheadSet */this._grammar.getMode().usesLookaheadSet() ? _defineProperty({}, _specialSymbols.EOF, true) : null);

    // Build the entire graph.
    this._rootItem.closure().goto();

    this._remap();

    _debug2.default.timeEnd('Building canonical collection');
    _debug2.default.log('Number of states in the collection: ' + this._states.size);

    // LALR(1) by converting to SLR(1), default fast LALR(1) mode.
    if (this._grammar.getMode().isLALR1()) {
      this._buildLALRBySLR();
    }

    // LALR(1) by compressing CLR(1): mostly for educational purposes, slow.
    else if (this._grammar.getMode().isLALR1ByCLR1()) {
        _debug2.default.time('Compressing CLR to LALR');
        this._compressCLRToLALR();
        _debug2.default.timeEnd('Compressing CLR to LALR');
        _debug2.default.log('Number of states after compression: ' + this._states.size);
      }
  }

  /**
   * Basic LALR(1) implementation compressing from CLR(1).
   *
   * This can be slow on complex grammars, and one can better
   * use LALR(1) by SLR(1) method.
   */


  _createClass(CanonicalCollection, [{
    key: '_compressCLRToLALR',
    value: function _compressCLRToLALR() {
      var _this = this;

      for (var lr0StateKey in this._lr0ItemSets) {
        var states = this._lr0ItemSets[lr0StateKey];

        var rootState = states[0];
        rootState.mergeLR0Items();

        while (states.length > 1) {
          var state = states.pop();
          state.mergeLR0Items();
          rootState.mergeWithState(state);
        }

        rootState.getItems().forEach(function (item) {
          // If the item was already connected, we should recalculate its
          // connection to the first state in the LR(0) states collection,
          // since only this state will be kept after states are merged.
          if (item.isConnected()) {
            var outerStates = _this.getLR0ItemsSet(item.goto());
            var outerState = outerStates[0];
            item.connect(outerState);
          }
        });
      }

      // After compression reassign new numbers to states.
      this._remap();
    }

    /**
     * Builds LALR(1) by SLR(1) grammar, and post-processes LR-items
     * by calculating needed lookahead sets.
     *
     * See good concise explanation of the algorithm here:
     * https://web.cs.dal.ca/~sjackson/lalr1.html
     */

  }, {
    key: '_buildLALRBySLR',
    value: function _buildLALRBySLR() {
      _debug2.default.time('Building LALR-by-SLR');
      this._buildExtendedLALR1Grammar();

      this._extendedFollowSets = new _setsGenerator2.default({
        grammar: this._extendedLALRGrammar
      }).getFollowSets();

      // Mutate the set with extended symbols to reflect the
      // symbols from the original grammar.
      for (var nonTerminal in this._extendedFollowSets) {
        var set = this._extendedFollowSets[nonTerminal];
        for (var symbol in set) {
          if (this._setsAliasMap.hasOwnProperty(symbol)) {
            set[this._setsAliasMap[symbol]] = true;
            delete set[symbol];
          }
        }
      }

      this._groupExtendedLALRByFinalSets();
      this._updateLALRItemReduceSet();
      _debug2.default.timeEnd('Building LALR-by-SLR');
    }

    /**
     * Groups extended LALR(1) by final sets.
     *
     * We merge extended rules, if they are from the same original
     * rule, and go to the same final set (has the same state number
     * in the very last symbol of RHS).
     */

  }, {
    key: '_groupExtendedLALRByFinalSets',
    value: function _groupExtendedLALRByFinalSets() {
      var _this2 = this;

      _debug2.default.time('LALR-by-SLR: Group extended productions by final sets');
      this._groupedFinalSets = {};

      this._extendedLALRGrammar.getProductions().forEach(function (production) {
        var LHS = production.getLHS();
        var RHS = production.getRHS();
        var lastSymbol = RHS[RHS.length - 1];
        var originalLHS = LHS.getOrignialSymbol();
        var finalSet = lastSymbol.getEndContext();

        if (!_this2._groupedFinalSets.hasOwnProperty(finalSet)) {
          _this2._groupedFinalSets[finalSet] = {};
        }

        if (!_this2._groupedFinalSets[finalSet].hasOwnProperty(originalLHS)) {
          _this2._groupedFinalSets[finalSet][originalLHS] = {};
        }

        // Merge follow sets.
        Object.assign(_this2._groupedFinalSets[finalSet][originalLHS], _this2._extendedFollowSets[LHS.getSymbol()]);
      });

      _debug2.default.timeEnd('LALR-by-SLR: Group extended productions by final sets');
    }

    /**
     * Updates the reduce sets for items in the LALR by SLR algorithm.
     */

  }, {
    key: '_updateLALRItemReduceSet',
    value: function _updateLALRItemReduceSet() {
      var _this3 = this;

      _debug2.default.time('LALR-by-SLR: Updating item reduce sets');
      var states = [].concat(_toConsumableArray(this._states));

      var _loop = function _loop(state) {
        states[state].getReduceItems().forEach(function (reduceItem) {
          var LHS = reduceItem.getProduction().getLHS().getSymbol();
          reduceItem.setReduceSet(_this3._groupedFinalSets[state][LHS]);
        });
      };

      for (var state in this._groupedFinalSets) {
        _loop(state);
      }
      _debug2.default.timeEnd('LALR-by-SLR: Updating item reduce sets');
    }

    /**
     * We use LALR(1) by SLR(1) algorithm here. Once we have built LR(0)
     * automation, we build the extended grammar, considering the context.
     * This context further results to needed lookahead set for LALR(1) which
     * is obtain as Follow(LHS), i.e. the same as in SLR(1).
     */

  }, {
    key: '_buildExtendedLALR1Grammar',
    value: function _buildExtendedLALR1Grammar() {
      _debug2.default.time('LALR-by-SLR: Building extended grammar for LALR');

      var extendedBnf = {};
      this._setsAliasMap = {};

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

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

          var items = state.getItems();
          var _iteratorNormalCompletion2 = true;
          var _didIteratorError2 = false;
          var _iteratorError2 = undefined;

          try {
            for (var _iterator2 = items[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
              var item = _step2.value;

              // Extended items are built only for beginning items.
              if (!item.isBeginning()) {
                continue;
              }
              // We traverse the full path of the item, in order to
              // to identify components with contexts.
              var current = item;
              var visited = new Set();

              var LHS = item.getProduction().getLHS().getSymbol();

              var lhsTransit = state.getTransitionOnSymbol(LHS);
              var lhsToState = lhsTransit ? lhsTransit.state.getNumber() : _specialSymbols.EOF;

              var extendedLHSSymbol = state.getNumber() + '|' + LHS + '|' + lhsToState;

              // Init the rules for the new LHS.
              if (!extendedBnf.hasOwnProperty(extendedLHSSymbol)) {
                extendedBnf[extendedLHSSymbol] = [];
              }

              var extendedRHS = [];

              while (current !== null && !visited.has(current)) {
                visited.add(visited);
                var transitionSymbol = current.getCurrentSymbol();

                if (transitionSymbol) {
                  var rawSymbol = transitionSymbol.getSymbol();
                  var fromState = current.getState().getNumber();
                  var toState = void 0;

                  // Epsilon reduces in this state.
                  if (transitionSymbol.isEpsilon()) {
                    toState = fromState;
                  } else if (current.getNext()) {
                    toState = current.getNext().getState().getNumber();
                  }

                  if (toState != null) {
                    var extendedRHSSymbol = fromState + '|' + rawSymbol + '|' + toState;
                    extendedRHS.push(extendedRHSSymbol);

                    // Collect extended token/terminal symbols as aliases of the
                    // original terminal symbols: this is needed to compute
                    // First/Follow sets as original symbols.
                    if (this._grammar.isTokenSymbol(rawSymbol)) {
                      this._setsAliasMap[extendedRHSSymbol] = rawSymbol;
                    }
                  }
                }

                current = current.getNext();
              }

              // Append the new RHS alternative.
              extendedBnf[extendedLHSSymbol].push(extendedRHS.join(' '));
            }
          } catch (err) {
            _didIteratorError2 = true;
            _iteratorError2 = err;
          } finally {
            try {
              if (!_iteratorNormalCompletion2 && _iterator2.return) {
                _iterator2.return();
              }
            } finally {
              if (_didIteratorError2) {
                throw _iteratorError2;
              }
            }
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      this._extendedLALRGrammar = new _grammar2.default({
        bnf: extendedBnf,
        mode: _grammarMode.MODES.LALR1_EXTENDED
      });

      _debug2.default.timeEnd('LALR-by-SLR: Building extended grammar for LALR');
    }
  }, {
    key: 'registerState',
    value: function registerState(state) {
      this._states.add(state);

      // Collect states by LR(0) items, to reuse and merge the same
      // states in case or LALR(1) mode.

      var lr0KeyForItems = _lrItem2.default.lr0KeyForItems(state.getKernelItems());

      if (!this._lr0ItemSets.hasOwnProperty(lr0KeyForItems)) {
        this._lr0ItemSets[lr0KeyForItems] = [];
      }

      this._lr0ItemSets[lr0KeyForItems].push(state);
    }
  }, {
    key: 'unregisterState',
    value: function unregisterState(state) {
      this._states.delete(state);

      var keyForItems = _lrItem2.default.keyForItems(state.getKernelItems());
      delete this._kernelSetsTransitions[keyForItems];

      var lr0KeyForItems = _lrItem2.default.lr0KeyForItems(state.getKernelItems());
      var lr0States = this._lr0ItemSets[lr0KeyForItems];
      var stateIndex = lr0States.indexOf(state);
      if (stateIndex !== -1) {
        lr0States.splice(stateIndex, 1);
      }
    }
  }, {
    key: 'getStates',
    value: function getStates() {
      return this._states;
    }
  }, {
    key: 'hasTranstionOnItems',
    value: function hasTranstionOnItems(items) {
      return !!this.getTranstionForItems(items);
    }
  }, {
    key: 'getTranstionForItems',
    value: function getTranstionForItems(items) {
      return this._kernelSetsTransitions[_lrItem2.default.keyForItems(items)];
    }
  }, {
    key: 'registerTranstionForItems',
    value: function registerTranstionForItems(items, outerState) {
      this._kernelSetsTransitions[_lrItem2.default.keyForItems(items)] = outerState;
    }

    /**
     * In LALR(1) there could be several states with the same
     * LR(0) items, but which differ only in lookahead symbols.
     * In this case we merge such states extending their lookaheads.
     */

  }, {
    key: 'getLR0ItemsSet',
    value: function getLR0ItemsSet(state) {
      return this._lr0ItemSets[_lrItem2.default.lr0KeyForItems(state.getKernelItems())];
    }
  }, {
    key: 'print',
    value: function print() {
      var _this4 = this;

      console.info('\nCanonical collection of LR items:');
      this._grammar.print();

      this._states.forEach(function (state) {
        var stateTags = [];

        if (state.isFinal()) {
          stateTags.push('final');

          if (state.isAccept()) {
            stateTags.push('accept');
          }
        }

        console.info('\nState ' + state.getNumber() + ':' + (stateTags.length > 0 ? ' (' + stateTags.join(', ') + ')' : ''));

        state.getItems().forEach(function (item) {
          return _this4._printItem(item, state);
        });
      });
    }
  }, {
    key: '_printItem',
    value: function _printItem(item, state) {
      var itemTags = [];

      if (state.isKernelItem(item)) {
        itemTags.push('kernel');
      }

      if (item.isShift()) {
        itemTags.push('shift');
      }

      if (item.isReduce()) {
        itemTags.push('reduce by production ' + item.getProduction().getNumber());
      }

      if (item.isFinal() && !item.isReduce()) {
        itemTags.push('accept');
      }

      if (item.goto()) {
        itemTags.push('goes to state ' + item.goto().getNumber());
      }

      console.info('  - ' + item.toString() + (itemTags.length > 0 ? ' (' + itemTags.join(', ') + ')' : ''));
    }
  }, {
    key: 'getRoot',
    value: function getRoot() {
      return this._rootItem;
    }
  }, {
    key: 'getStartingState',
    value: function getStartingState() {
      return this.getRoot().getState();
    }
  }, {
    key: '_remap',
    value: function _remap() {
      var number = 0;
      var _iteratorNormalCompletion3 = true;
      var _didIteratorError3 = false;
      var _iteratorError3 = undefined;

      try {
        for (var _iterator3 = this._states[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
          var state = _step3.value;

          state.setNumber(number++);
        }
      } catch (err) {
        _didIteratorError3 = true;
        _iteratorError3 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion3 && _iterator3.return) {
            _iterator3.return();
          }
        } finally {
          if (_didIteratorError3) {
            throw _iteratorError3;
          }
        }
      }
    }
  }]);

  return CanonicalCollection;
}();

exports.default = CanonicalCollection;