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

var _state2 = _interopRequireDefault(_state);

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

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

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

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

/**
 * An LRItem is built for a production at particular
 * dot position. The kernel item (for the augmented production)
 * builds the canonical collection, recursively applying
 * "closure" and "goto" operations.
 *
 * There are two types of LR items: LR(0) items, which are used by
 * LR(0) and SLR(1) parsers, and LR(1) items, which are used by
 * LALR(1) and CLR(1) parsers. An LR(1) is defined as:
 *
 * LR(1) = LR(0) + lookahead set.
 */
var LRItem = function () {
  function LRItem(production, dotPosition, grammar, canonicalCollection, setsGenerator) {
    var lookaheadSet = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;

    _classCallCheck(this, LRItem);

    this._key = LRItem.keyForItem(production, dotPosition, lookaheadSet);

    // For LR(1) item, same as key, but withough lookaheads.
    this._lr0Key = LRItem.keyForItem(production, dotPosition, null);

    this._production = production;
    this._RHS = this._production.getRHS();
    this._dotPosition = dotPosition;
    this._grammar = grammar;
    this._canonicalCollection = canonicalCollection;
    this._setsGenerator = setsGenerator;

    // The state this item belongs to.
    this._state = null;

    // The state this item goes to.
    this._outerState = null;

    this._reduceSet = null;

    // Next (advanced) item for this production.
    this._next = null;

    // LR(1) items maintain lookahead.
    this._lookaheadSet = lookaheadSet;
  }

  /**
   * Returns productions of this item.
   */


  _createClass(LRItem, [{
    key: 'getProduction',
    value: function getProduction() {
      return this._production;
    }

    /**
     * Returns dot position.
     */

  }, {
    key: 'getDotPosition',
    value: function getDotPosition() {
      return this._dotPosition;
    }

    /**
     * Whether this item should be closured.
     */

  }, {
    key: 'shouldClosure',
    value: function shouldClosure() {
      return !this._closured && !this.isFinal() && this._grammar.isNonTerminal(this.getCurrentSymbol());
    }

    /**
     * Whether transition from this item does "shift" action.
     */

  }, {
    key: 'isShift',
    value: function isShift() {
      return !this.isFinal() && this._grammar.isTokenSymbol(this.getCurrentSymbol());
    }

    /**
     * Whether transition from this item does "reduce" action.
     */

  }, {
    key: 'isReduce',
    value: function isReduce() {
      return this.isFinal() && !this.getProduction().isAugmented();
    }

    /**
     * Whether this item is already connected to a closure.
     */

  }, {
    key: 'isConnected',
    value: function isConnected() {
      return this._outerState !== null;
    }

    /**
     * Whether this item is a beginning item, i.e. has the dot
     * cursor at the very begin.
     */

  }, {
    key: 'isBeginning',
    value: function isBeginning() {
      return this._dotPosition === 0;
    }

    /**
     * Returns next item of this item.
     */

  }, {
    key: 'getNext',
    value: function getNext() {
      return this._next;
    }

    /**
     * Connects this item to the outer closure state.
     */

  }, {
    key: 'connect',
    value: function connect(state) {
      this._outerState = state;

      // Update next item.
      var nextKey = LRItem.keyForItem(this._production, this._dotPosition + 1, this._lookaheadSet);

      var next = state.getItemByKey(nextKey);
      this._next = next;
    }

    /**
     * Returns the closure object for this item.
     */

  }, {
    key: 'getState',
    value: function getState() {
      return this._state;
    }

    /**
     * Sets the state to which this item belongs to.
     */

  }, {
    key: 'setState',
    value: function setState(state) {
      return this._state = state;
    }

    /**
     * Applies closure operation for this item.
     */

  }, {
    key: 'closure',
    value: function closure() {
      var _this = this;

      if (!this.shouldClosure()) {
        return;
      }

      // Main kernel item for the augmented production, that creates a new state.
      if (!this._state) {
        this._state = new _state2.default(
        /* kernelItems */[this],
        /* grammar */this._grammar,
        /* canonicalCollection */this._canonicalCollection,
        /* setsGenerator */this._setsGenerator);
      }

      var productionsForSymbol = this._grammar.getProductionsForSymbol(this.getCurrentSymbol().getSymbol());

      var addedItems = productionsForSymbol.map(function (production) {
        return new LRItem(production,
        /* dotPosition */0, _this._grammar, _this._canonicalCollection, _this._setsGenerator,
        /* lookaheadSet */_this._calculateLookaheadSet());
      });

      this._closured = true;
      this._state.addItems(addedItems);

      return this._state;
    }

    /**
     * Calculates a lookahead set for an added item
     * in the closure. The lookahead set is determined from
     * the previous item as First(followPart + previousLookahead).
     *
     * A -> a • B ß, a/b
     * B -> ∂, c/d
     *
     * where c/d is First(ß a b), and is the lookahead set.
     * If ß is ε, then a/b is lookahead set.
     */

  }, {
    key: '_calculateLookaheadSet',
    value: function _calculateLookaheadSet() {
      if (!this._grammar.getMode().usesLookaheadSet()) {
        return null;
      }

      var lookaheadSet = void 0;

      var followPosition = this._dotPosition + 1;
      var RHS = this.getProduction().getRHS();

      if (followPosition < RHS.length) {
        var lookaheadPart = RHS.slice(followPosition);
        lookaheadSet = this._setsGenerator.firstOfRHS(lookaheadPart);
      }

      var containsEpsilon = false;

      if (lookaheadSet) {
        containsEpsilon = lookaheadSet.hasOwnProperty(_specialSymbols.EPSILON);
        delete lookaheadSet[_specialSymbols.EPSILON];
      } else {
        lookaheadSet = {};
      }

      // If no follow part, or we got an empty set, use lookahead of
      // the previous item. The previous part should also be merged if
      // the set contains epsilon.
      if (Object.keys(lookaheadSet).length === 0 || containsEpsilon) {
        lookaheadSet = Object.assign({}, lookaheadSet, this.getLookaheadSet());
      }

      return lookaheadSet;
    }

    /**
     * Goto operation from this item.
     */

  }, {
    key: 'goto',
    value: function goto() {
      if (!this._outerState) {
        this._state.goto();
      }

      return this._outerState;
    }

    /**
     * The symbol at the dot position.
     */

  }, {
    key: 'getCurrentSymbol',
    value: function getCurrentSymbol() {
      return this._RHS[this._dotPosition];
    }

    /**
     * Whether this reduce item conflicts with a shift symbol.
     */

  }, {
    key: 'conflictsWithShiftSymbol',
    value: function conflictsWithShiftSymbol(symbol) {
      this._assertReduce();

      var reduceSet = this.calculateReduceSet();

      if (reduceSet === true) {
        return true;
      }

      return Object.keys(reduceSet).indexOf(symbol.getSymbol()) !== -1;
    }

    /**
     * Whether this reduce item conflicts with another reduce item
     * (this may happen if the lookaheads intersect).
     */

  }, {
    key: 'conflictsWithReduceItem',
    value: function conflictsWithReduceItem(reduceItem) {
      this._assertReduce();

      var thisReduceSet = Object.keys(this.calculateReduceSet());

      if (thisReduceSet === true) {
        return true;
      }

      var thatReduceSet = Object.keys(reduceItem.calculateReduceSet());

      var lookaheadsIntersection = thisReduceSet.filter(function (symbol) {
        return thatReduceSet.indexOf(symbol) !== -1;
      });

      return lookaheadsIntersection.length !== 0;
    }
  }, {
    key: '_assertReduce',
    value: function _assertReduce() {
      if (!this.isReduce()) {
        throw new Error('Item ' + this.getKey() + ' is not a reduce item.');
      }
    }

    /**
     * Whether we have seen the whole production.
     *
     * The item `S -> • ε` (or its short equivalent `S -> •`) is final as well.
     */

  }, {
    key: 'isFinal',
    value: function isFinal() {
      return this._dotPosition === this._RHS.length || this.isEpsilonTransition();
    }

    /**
     * Whether this item does transition on ε.
     */

  }, {
    key: 'isEpsilonTransition',
    value: function isEpsilonTransition() {
      var currentSymbol = this.getCurrentSymbol();
      return !!(currentSymbol && currentSymbol.isSymbol(_specialSymbols.EPSILON));
    }

    /**
     * Returns a reduce set (usually a follow lookahead set)
     * for this item. LR0 mode reduces for every terminal,
     * SLR(1) uses Follow(LHS), and LALR(1)/CLR(1) lookaheads
     * set which is First(RHS) of the current symbol, including
     * all lookaheads from previous items.
     */

  }, {
    key: 'getReduceSet',
    value: function getReduceSet() {
      if (!this._reduceSet) {
        this.setReduceSet(this.calculateReduceSet());
      }
      return this._reduceSet;
    }

    /**
     * Sets explicit reduce set (usually in building LALR by SLR).
     */

  }, {
    key: 'setReduceSet',
    value: function setReduceSet(reduceSet) {
      this._reduceSet = reduceSet;
    }
  }, {
    key: 'calculateReduceSet',
    value: function calculateReduceSet() {
      switch (this._grammar.getMode().getRaw()) {
        case _grammarMode.MODES.LR0:
          // LR0 reduces for all terminals, special `true` value.
          return true;

        case _grammarMode.MODES.LALR1:
        case _grammarMode.MODES.LALR1_BY_SLR1:
          // In "LALR(1) by SLR(1)" the reduce set is already calculated
          // in post-processing of the LR(0) automation.
          return this._reduceSet;

        case _grammarMode.MODES.SLR1:
          {
            // SLR(1) reduces in Follow(LHS).
            var LHS = this.getProduction().getLHS();
            return this._setsGenerator.followOf(LHS);
          }

        case _grammarMode.MODES.CLR1:
        case _grammarMode.MODES.LALR1_BY_CLR1:
          // CLR(1) consider lookahead of the LR(1) item.
          return Object.assign({}, this._lookaheadSet);

        default:
          throw new Error('Unexpected grammar mode ' + this._grammar.getMode() + '.');
      }
    }

    /**
     * Returns lookahead set (for LR(1) items).
     */

  }, {
    key: 'getLookaheadSet',
    value: function getLookaheadSet() {
      return this._lookaheadSet;
    }

    /**
     * Sets a lookahead set to this item (can happen when we merge
     * states in LALR(1), and need to recalculate lookaheads.
     */

  }, {
    key: 'setLookaheadSet',
    value: function setLookaheadSet(lookaheadSet) {
      // Extend the set.
      this._lookaheadSet = lookaheadSet;

      // Reset toStringKey (will be recalculated on next call to `toString`).
      this._toStringKey = null;

      // And rebuild the key since lookahead set is changed.
      this._key = LRItem.keyForItem(this._production, this._dotPosition, this._lookaheadSet);
    }

    /**
     * In LALR(1) mode we merge the states with the same kernel items, which
     * differ only in lookaheads set. The merging is done by extending the
     * lookaheads of the items.
     */

  }, {
    key: 'mergeLookaheadSet',
    value: function mergeLookaheadSet(lookaheadSet) {
      // Extend the set.
      this.setLookaheadSet(Object.assign(this._lookaheadSet, lookaheadSet));
    }

    /**
     * Returns serialized representation of an item.
     * E.g. `A -> • a A, c/d/e`.
     */

  }, {
    key: 'getKey',
    value: function getKey() {
      return this._key;
    }

    /**
     * Same as `getKey`, but don't consider lookaheads of LR(1) item,
     * returning only its LR(0) representation.
     *
     * LR(1): `A -> • a A, c/d/e`
     * LR(0): `A -> • a A`
     */

  }, {
    key: 'getLR0Key',
    value: function getLR0Key() {
      return this._lr0Key;
    }
  }, {
    key: 'toString',
    value: function toString() {
      if (!this._toStringKey) {
        this._toStringKey = this._getToStringKey();
      }
      return this._toStringKey;
    }
  }, {
    key: '_getToStringKey',


    /**
     * Returns key to be displayed in toString.
     */
    value: function _getToStringKey() {
      var RHS = this._RHS.map(function (symbol) {
        return symbol.getSymbol();
      });
      RHS.splice(this._dotPosition, 0, '•');

      var lookaheads = '';
      if (this._lookaheadSet) {
        lookaheads = ', #lookaheads= ' + JSON.stringify(Object.keys(this._lookaheadSet));
      }

      return this._production.getLHS().getSymbol() + ' -> ' + ('' + RHS.join(' ') + lookaheads);
    }

    /**
     * Returns a key for a set of items.
     */

  }, {
    key: 'advance',


    /**
     * Returns an a new item with an advanced dot position.
     */
    value: function advance() {
      var next = new LRItem(this._production,
      /* dotPosition */this._dotPosition + 1, this._grammar, this._canonicalCollection, this._setsGenerator,
      // On goto transition lookaheads set doesn't change.
      /* lookaheadSet */this._grammar.getMode().usesLookaheadSet() ? Object.assign({}, this.getLookaheadSet()) : null);
      this._next = next;
      return next;
    }
  }], [{
    key: 'keyForItem',
    value: function keyForItem(production, dotPosition) {
      var lookaheadSet = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;

      return production.getNumber() + '|' + dotPosition + (lookaheadSet ? '|' + Object.keys(lookaheadSet).join('|') : '');
    }
  }, {
    key: 'keyForItems',
    value: function keyForItems(items) {
      if (!items.key) {
        items.key = items.map(function (item) {
          return item.getKey();
        }).sort().join('|');
      }
      return items.key;
    }

    /**
     * Returns an LR(0) key for a set of items (duplicates are removed).
     */

  }, {
    key: 'lr0KeyForItems',
    value: function lr0KeyForItems(items) {
      if (!items.lr0Key) {
        var keysMap = {};
        items.forEach(function (item) {
          return keysMap[item.getLR0Key()] = true;
        });
        items.lr0Key = Object.keys(keysMap).sort().join('|');
      }
      return items.lr0Key;
    }
  }]);

  return LRItem;
}();

exports.default = LRItem;