'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 _codeUnit = require('../code-unit');

var _codeUnit2 = _interopRequireDefault(_codeUnit);

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

var _grammarSymbol2 = _interopRequireDefault(_grammarSymbol);

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

var _colors = require('colors');

var _colors2 = _interopRequireDefault(_colors);

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

/**
 * A production in BNF grammar.
 */
var Production = function () {
  /**
   * Receives a raw production in a view of:
   *
   * LHS -> RHS or a short alternative
   *      | RHS if the LHS is the same.
   */
  function Production(LHS, RHS, number, semanticAction, isShort, grammar, precedence) {
    _classCallCheck(this, Production);

    this._rawLHS = LHS;
    this._rawRHS = RHS;
    this._number = number;
    this._isAugmented = number === 0;
    this._isShort = !!isShort;
    this._grammar = grammar;
    this._normalize();

    if (semanticAction == null) {
      semanticAction = this._createDefaultSemanticAction();
    }

    this._orginialSemanticAction = semanticAction;
    this._rawSemanticAction = this._rewriteNamedArg(semanticAction);
    this._semanticAction = this._buildSemanticAction(this._rawSemanticAction);
    this._precedence = precedence || this._calculatePrecedence();
  }

  /**
   * Creates default semantic action for simple productions.
   */


  _createClass(Production, [{
    key: '_createDefaultSemanticAction',
    value: function _createDefaultSemanticAction() {
      if (this.getRHS().length !== 1 || this.isEpsilon()) {
        return null;
      }

      return '$$ = $1';
    }

    /**
     * Rewrites named arguments to positioned ones.
     * $foo -> $1, ...
     */

  }, {
    key: '_rewriteNamedArg',
    value: function _rewriteNamedArg(semanticAction) {
      if (!semanticAction) {
        return null;
      }

      var RHS = this.getRHS();
      var idRe = /[a-zA-Z][a-zA-Z0-9]*/;

      for (var i = 0; i < RHS.length; i++) {
        var symbol = RHS[i].getSymbol();

        if (!idRe.test(symbol)) {
          continue;
        }

        var index = i + 1;
        var symbolRe = new RegExp('(\\$|@)' + symbol + '\\b', 'g');

        semanticAction = semanticAction.replace(symbolRe, '$1' + index);
      }

      return semanticAction;
    }

    /**
     * Returns number of the production
     * in the grammar.
     */

  }, {
    key: 'getNumber',
    value: function getNumber() {
      return this._number;
    }

    /**
     * Whether this production is augmented.
     */

  }, {
    key: 'isAugmented',
    value: function isAugmented() {
      return this._isAugmented;
    }

    /**
     * Returns LHS symbol.
     */

  }, {
    key: 'getLHS',
    value: function getLHS() {
      return this._LHS;
    }

    /**
     * Returns an array of symbols on RHS (aka "handle").
     */

  }, {
    key: 'getRHS',
    value: function getRHS() {
      return this._RHS;
    }

    /**
     * Same as `getRHS`, but returns raw symbols.
     */

  }, {
    key: 'getRHSSymbols',
    value: function getRHSSymbols() {
      if (!this._rhsSymbols) {
        this._rhsSymbols = this._RHS.map(function (symbol) {
          return symbol.getSymbol();
        });
      }
      return this._rhsSymbols;
    }

    /**
     * A map for faster searches whether a symbol is used on RHS.
     */

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

      if (!this._rhsSymbolsMap) {
        this._rhsSymbolsMap = {};
        this._RHS.forEach(function (symbol) {
          return _this._rhsSymbolsMap[symbol.getSymbol()] = true;
        });
      }
      return this._rhsSymbolsMap;
    }

    /**
     * Returns precedence of this production.
     */

  }, {
    key: 'getPrecedence',
    value: function getPrecedence() {
      return this._precedence;
    }

    /**
     * Returns original semantic action.
     */

  }, {
    key: 'getOriginalSemanticAction',
    value: function getOriginalSemanticAction() {
      return this._orginialSemanticAction;
    }

    /**
     * Returns semantic action string.
     */

  }, {
    key: 'getRawSemanticAction',
    value: function getRawSemanticAction() {
      return this._rawSemanticAction;
    }

    /**
     * Returns semantic action function.
     */

  }, {
    key: 'getSemanticAction',
    value: function getSemanticAction() {
      return this._semanticAction;
    }

    /**
     * Whether this production has semantic action.
     */

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

    /**
     * Executes semantic action.
     */

  }, {
    key: 'runSemanticAction',
    value: function runSemanticAction(args) {
      if (!this._semanticAction) {
        return;
      }
      return this._semanticAction.apply(this, _toConsumableArray(args));
    }

    /**
     * Whether this production is epsilon.
     */

  }, {
    key: 'isEpsilon',
    value: function isEpsilon() {
      var RHS = this.getRHS();
      return RHS.length === 1 && RHS[0].isEpsilon();
    }

    /**
     * String representation.
     */

  }, {
    key: 'toString',
    value: function toString() {
      return this._toKey(this._isShort);
    }

    /**
     * String representation in full notation.
     */

  }, {
    key: 'toFullString',
    value: function toFullString() {
      return this._toKey(false);
    }
  }, {
    key: '_toKey',
    value: function _toKey() {
      var isShort = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;

      var LHS = this._LHS.getSymbol();

      var RHS = this._RHS.map(function (symbol) {
        return symbol.getSymbol();
      }).join(' ');

      var pad = Array(LHS.length + '->'.length).join(' ');

      return isShort ? pad + ' | ' + RHS : LHS + ' -> ' + RHS;
    }

    /**
     * Constructs semantic action based on the RHS length,
     * each stack entry which corresponds to a symbol is available
     * as $1, $2, $3, etc. arguments. The result is $$.
     */

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

      if (!semanticAction) {
        return null;
      }

      // Generate the function handler only for JS language.
      try {
        var handler = _codeUnit2.default.createProductionHandler({
          production: this,
          captureLocations: this._grammar.shouldCaptureLocations()
        });

        return function () {
          // Executing a handler mutates $$ variable, return it.
          try {
            handler.apply(undefined, arguments);
          } catch (e) {
            console.error(_colors2.default.red('\nError in handler for production ') + _colors2.default.bold(_this2.toFullString()) + ':\n\n' + _this2.getOriginalSemanticAction() + '\n');
            throw e;
          }
          return _codeUnit2.default.getSandbox().__;
        };
      } catch (e) {
        /* And skip for other languages, which use raw handler in generator */
      }
    }
  }, {
    key: '_normalize',
    value: function _normalize() {
      var LHS = _grammarSymbol2.default.get(this._rawLHS);
      var RHS = [];

      // If no RHS provided, assume it's ε. We support
      // both formats, explicit: F -> ε, and implicit: F ->

      if (!this._rawRHS) {
        RHS.push(_grammarSymbol2.default.get(_specialSymbols.EPSILON));
      } else {
        var rhsProd = this._rawRHS.split(/\s+/);
        for (var i = 0; i < rhsProd.length; i++) {
          if (rhsProd[i] === '"' && rhsProd[i + 1] === '"') {
            RHS.push(_grammarSymbol2.default.get('" "'));
            i++;
          } else {
            RHS.push(_grammarSymbol2.default.get(rhsProd[i]));
          }
        }
      }

      this._LHS = LHS;
      this._RHS = RHS;
    }
  }, {
    key: '_calculatePrecedence',
    value: function _calculatePrecedence() {
      var operators = this._grammar.getOperators();

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

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

          var symbol = grammarSymbol.getSymbol();

          if (symbol in operators) {
            return operators[symbol].precedence;
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }

      return 0;
    }
  }]);

  return Production;
}();

exports.default = Production;