'use strict';

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

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

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 _canonicalCollection = require('./canonical-collection');

var _canonicalCollection2 = _interopRequireDefault(_canonicalCollection);

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

var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);

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

/**
 * The LR parsing table is built by traversing the graph of the
 * canonical collection of LR items. Rows of the table correspond to
 * the state (closure) numbers, and columns are terminals (for "action"
 * part) and non-terminals (for "goto" part).
 *
 * For the example grammar (where S' -> S is the augmented production):
 *
 *  +------------+
 *  | S' -> S    |
 *  +------------+
 *  | S -> A A   |
 *  | A -> "a" A |
 *  |    | "b"   |
 *  +------------+
 *
 * The LR(0) parsing table, which is used by LR(0) and SLR(1) parsers,
 * looks like this:
 *
 * State      Action            Goto
 * +---+-----+-----+-----++-----+-----+
 * |   │ "a" │ "b" │  $  ││  S  │  A  │
 * +---+-----+-----+-----++-----+-----+
 * | 0 │ s2  │ s4  │     ││  3  │  1  │
 * +---+-----+-----+-----++-----+-----+
 * | 1 │ s2  │ s4  │     ││     │  5  │
 * +---+-----+-----+-----++-----+-----+
 * | 2 │ s2  │ s4  │     ││     │  6  │
 * +---+-----+-----+-----++-----+-----+
 * | 3 │     │     │ acc ││     │     │
 * +---+-----+-----+-----++-----+-----+
 * | 4 │ r3  │ r3  │ r3  ││     │     │
 * +---+-----+-----+-----++-----+-----+
 * | 5 │ r1  │ r1  │ r1  ││     │     │
 * +---+-----+-----+-----++-----+-----+
 * | 6 │ r2  │ r2  │ r2  ││     │     │
 * +---+-----+-----+-----++-----+-----+
 *
 * Note: for LR(1) items used by LALR(1) and CLR(1) number of reduce steps
 * may decrease. Also number of states may increase in case of CLR(1).
 *
 *   - State: number of a state (closure) in the graph
 *
 *   Action:
 *
 *   - s<n> - Shift<n>: "shift" action (move a symbol onto the stack) and
 *     transit to the state n next
 *
 *   - r<k> - Reduce<k>: "reduce" action (replace RHS of a production <k>
 *     which is on top of the stack, with its LHS.
 *
 *   - acc - "accept" action, successful parse.
 *
 *   Goto:
 *
 *   - <n> - goto to state <n> in the graph.
 *
 * Examples:
 *
 *   - 0:A -> 3: if we're in the state 0, and see non-terminal A, go to
 *     the state 3.
 *
 *   - 1:"a" -> s2: if we're in the state 1, and see the "a" terminal,
 *     shift it from buffer onto the stack, and go to the state 2.
 *
 *   - 5:"b" -> r2: if we're in the state 5, and see the "b" terminal,
 *     reduce the RHS of the production 2 on top of the stack to its
 *     LHS non-terminal.
 */

/**
 * Type of an entry in the parsing table.
 */
var EntryType = {
  ERROR: 0,
  GOTO: 1,
  SHIFT: 2,
  REDUCE: 3,
  ACCEPT: 4,
  SR_CONFLICT: 5,
  RR_CONFLICT: 6
};

/**
 * LR parsing table class.
 */

var LRParsingTable = function () {
  /**
   * The table is built from the canonical collection,
   * which was built for the specific grammar.
   */
  function LRParsingTable(_ref) {
    var _ref$canonicalCollect = _ref.canonicalCollection,
        canonicalCollection = _ref$canonicalCollect === undefined ? null : _ref$canonicalCollect,
        grammar = _ref.grammar,
        _ref$resolveConflicts = _ref.resolveConflicts,
        resolveConflicts = _ref$resolveConflicts === undefined ? false : _ref$resolveConflicts;

    _classCallCheck(this, LRParsingTable);

    this._grammar = grammar;
    this._canonicalCollection = canonicalCollection;
    this._shouldResolveConflicts = resolveConflicts;

    if (!this._canonicalCollection) {
      this._canonicalCollection = new _canonicalCollection2.default({
        grammar: this._grammar
      });
    }

    // Stores conflicts data.
    this._conflictsData = {};

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

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

    this._goto = grammar.getNonTerminals();
    this._table = {};
    this._build();
    _debug2.default.timeEnd('Building LR parsing table');
  }

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

    /**
     * Returns conflicts data.
     */

  }, {
    key: 'getConflictsData',
    value: function getConflictsData() {
      return this._conflictsData;
    }
  }, {
    key: 'print',
    value: function print() {
      var _this = this;

      this._grammar.print();

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

      var actionSymbols = this._action.map(function (actionSymbol) {
        return actionSymbol.getSymbol();
      });

      var nonTerminals = this._grammar.getNonTerminals().map(function (nonTerminal) {
        return nonTerminal.getSymbol();
      });

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

      var _loop = function _loop(stateNumber) {
        var tableRow = _this._table[stateNumber];
        var stateLabel = _colors2.default.blue(stateNumber);
        var row = _defineProperty({}, stateLabel, []);

        // Action part.
        actionSymbols.forEach(function (actionSymbol) {
          var entry = tableRow[actionSymbol] || '';

          if (_this._hasConflict(entry)) {
            entry = _colors2.default.red(entry);
          } else if (entry === 'acc') {
            entry = _colors2.default.green(entry);
          }

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

        // Goto part.
        nonTerminals.forEach(function (nonTerminal) {
          row[stateLabel].push(tableRow[nonTerminal] || '');
        });

        printer.push(row);
      };

      for (var stateNumber in this._table) {
        _loop(stateNumber);
      }

      console.info(printer.toString());
      console.info('');
    }
  }, {
    key: '_hasConflict',
    value: function _hasConflict(entry) {
      var entryType = LRParsingTable.getEntryType(entry);
      return entryType === EntryType.RR_CONFLICT || entryType === EntryType.SR_CONFLICT;
    }
  }, {
    key: '_build',
    value: function _build() {
      var _this2 = this;

      this._canonicalCollection.getStates().forEach(function (state) {
        // Fill actions and goto for this state (row).
        var row = _this2._table[state.getNumber()] = {};

        state.getItems().forEach(function (item) {
          // For final item we should "reduce". In LR(0) type we
          // reduce unconditionally for every terminal, in other types
          // e.g. SLR(1) consider lookahead (follow) sets.
          if (item.isFinal()) {
            var production = item.getProduction();

            // For the final item of the augmented production,
            // the action is "acc" (accept).
            if (production.isAugmented()) {
              row[_specialSymbols.EOF] = 'acc';
            } else {
              // Otherwise, reduce.
              _this2._action.forEach(function (terminal) {
                if (_this2._shouldReduce(item, terminal)) {
                  _this2._putActionEntry(row, terminal.getSymbol(), 'r' + production.getNumber());
                }
              });
            }
          } else {
            var transitionSymbol = item.getCurrentSymbol();
            var nextState = item.goto().getNumber();

            // Other terminals do "shift" action and go to the next state,
            // and non-terminals just go to the next
            if (_this2._grammar.isTokenSymbol(transitionSymbol)) {
              _this2._putActionEntry(row, transitionSymbol.getSymbol(), 's' + nextState);
            } else {
              row[transitionSymbol.getSymbol()] = nextState;
            }
          }
        });

        // Resolve conflicts, if any.
        _this2._resolveConflicts(state, row);
      });
    }
  }, {
    key: '_getStateConflictData',
    value: function _getStateConflictData(state) {
      var stateNumber = state.getNumber();

      if (!this._conflictsData.hasOwnProperty(stateNumber)) {
        this._conflictsData[stateNumber] = {};
      }

      return this._conflictsData[stateNumber];
    }
  }, {
    key: '_initSymbolConflictData',
    value: function _initSymbolConflictData(state, symbol, conflict) {
      this._getStateConflictData(state)[symbol] = {
        conflict: conflict,
        resolved: false
      };
    }
  }, {
    key: '_resolveConflicts',
    value: function _resolveConflicts(state, row) {
      for (var symbol in row) {
        var entry = row[symbol];
        var entryType = LRParsingTable.getEntryType(entry);

        // Shift-reduce.
        if (entryType === EntryType.SR_CONFLICT) {
          this._initSymbolConflictData(state, symbol, entry);
          this._resolveSRConflict(state, row, symbol);
        }

        // Reduce-reduce.
        else if (entryType === EntryType.RR_CONFLICT) {
            this._initSymbolConflictData(state, symbol, entry);
            this._resolveRRConflict(state, row, symbol);
          }
      }
    }
  }, {
    key: '_resolveSRConflict',
    value: function _resolveSRConflict(state, row, symbol) {
      var entry = row[symbol];
      var operators = this._grammar.getOperators();

      var _splitSRParts = this.splitSRParts(entry),
          _splitSRParts2 = _slicedToArray(_splitSRParts, 2),
          reducePart = _splitSRParts2[0],
          shiftPart = _splitSRParts2[1];

      // Default resolution is to shift if no precedence is specified.


      if (!operators.hasOwnProperty(symbol)) {
        if (this._shouldResolveConflicts) {
          row[symbol] = shiftPart;
          this._getStateConflictData(state)[symbol].resolved = 'no precedence, shift by default';
        }
        return;
      }

      // Else, working with operators precedence.

      var _operators$symbol = operators[symbol],
          symbolPrecedence = _operators$symbol.precedence,
          symbolAssoc = _operators$symbol.assoc;


      var productionPrecedence = this._grammar.getProduction(reducePart.slice(1)).getPrecedence();

      var symbolConflictData = this._getStateConflictData(state)[symbol];

      // 1. If production's precedence is higher, the choice is to reduce:
      //
      //   R: E -> E * E • (reduce since `*` > `+`)
      //   S: E -> E • + E
      //
      if (productionPrecedence > symbolPrecedence) {
        row[symbol] = reducePart;
        symbolConflictData.resolved = 'reduce (higher production precedence)';
      }

      // 2. If the symbol's precedence is higher, the choice is to shift:
      //
      //   E -> E + E •
      //   E -> E • * E (shift since `*` > `+`)
      //
      else if (symbolPrecedence > productionPrecedence) {
          row[symbol] = shiftPart;
          symbolConflictData.resolved = 'shift (higher symbol precedence)';
        }

        // 3. If they have equal precedence, the choice is made based on the
        // associativity of that precedence level:
        //
        //   E -> E * E • (choose to reduce since `*` is left-associative)
        //   E -> E • * E
        //
        // This case we want `id * id * id` to be left-associative, i.e.
        // `(id * id) * id`, but not right-associative, that would be
        // `id * (id * id)`.
        //
        else if (productionPrecedence === symbolPrecedence && productionPrecedence !== 0 && symbolPrecedence !== 0) {
            // Left-assoc.
            if (symbolAssoc === 'left') {
              row[symbol] = reducePart;
              symbolConflictData.resolved = 'reduce (same precedence, left-assoc)';
            } else if (symbolAssoc === 'right') {
              row[symbol] = shiftPart;
              symbolConflictData.resolved = 'shift (same precedence, right-assoc)';
            } else if (symbolAssoc === 'nonassoc') {
              // No action on `nonassoc`.
              delete row[symbol];
              symbolConflictData.resolved = 'removed conflict for nonassoc';
            }
          }
    }
  }, {
    key: '_resolveRRConflict',
    value: function _resolveRRConflict(state, row, symbol) {
      if (!this._shouldResolveConflicts) {
        return;
      }

      var entry = row[symbol];

      var _entry$split = entry.split('/'),
          _entry$split2 = _slicedToArray(_entry$split, 2),
          r1 = _entry$split2[0],
          r2 = _entry$split2[1];

      var symbolConflictData = this._getStateConflictData(state)[symbol];

      // R/R conflicts are resolved by choosing a production that
      // goes first in the grammar (i.e. its number is smaller).
      row[symbol] = Number(r1.slice(1)) < Number(r2.slice(1)) ? r1 : r2;

      symbolConflictData.resolved = 'first in order production is chosen ' + row[symbol].slice(1);
    }
  }, {
    key: 'splitSRParts',
    value: function splitSRParts(entry) {
      var srConflict = entry.split('/');

      return LRParsingTable.getEntryType(srConflict[0]) === EntryType.REDUCE ? [srConflict[0], srConflict[1]] : [srConflict[1], srConflict[0]];
    }
  }, {
    key: '_shouldReduce',
    value: function _shouldReduce(item, terminal) {
      var reduceSet = item.getReduceSet();

      // LR(0) reduces for all terminals.
      if (reduceSet === true) {
        return true;
      }

      // SLR(1) considers Follow(LHS), LALR(1) and CLR(1)
      // considers lookahead sets.
      return reduceSet.hasOwnProperty(terminal.getSymbol());
    }
  }, {
    key: '_putActionEntry',
    value: function _putActionEntry(row, column, entry) {
      var previousEntry = row[column];

      // In case we have a transition on the same
      // symbol, and an action was already registered.
      if (previousEntry === entry) {
        return;
      }

      // Register an entry handling possible "shift-reduce" (s/r)
      // or "reduce-reduce" (r/r) conflicts.

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

      row[column] = entry;
    }
  }], [{
    key: 'getEntryType',
    value: function getEntryType(entry) {
      if (typeof entry === 'number') {
        return EntryType.GOTO;
      } else if (entry.indexOf('/') !== -1) {
        return entry.indexOf('s') !== -1 ? EntryType.SR_CONFLICT : EntryType.RR_CONFLICT;
      } else if (entry[0] === 's') {
        return EntryType.SHIFT;
      } else if (entry[0] === 'r') {
        return EntryType.REDUCE;
      } else if (entry === 'acc') {
        return EntryType.ACCEPT;
      }

      return EntryType.ERROR;
    }
  }, {
    key: 'EntryType',
    get: function get() {
      return EntryType;
    }
  }]);

  return LRParsingTable;
}();

exports.default = LRParsingTable;