'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 _lexRule = require('./lex-rule');

var _lexRule2 = _interopRequireDefault(_lexRule);

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

/**
 * Standard macro symbols.
 */
var StandardMacros = {
  /**
   * End of file macro, matches `$` at the end of the parsing string.
   */
  '<<EOF>>': _specialSymbols.EOF
};

/**
 * Class encapsulates operations with a lexical grammar.
 */

var LexGrammar = function () {
  /**
   * A lexical grammar is used for a string tokenization. An example of the
   * lexical grammar data:
   *
   * {
   *   "macros": {
   *     "digit": "[0-9]",
   *   },
   *
   *   "rules": [
   *     ["a", "return 'a';"],
   *     ["\\(", "return '(';"],
   *     ["\\)", "return ')';"],
   *     ["\\+", "return '+';"],
   *     ["{digit}+(\\.{digit}+)?\\b", "return 'NUMBER';"],
   *
   *     // A rule with start conditions. Such rules are matched only
   *     // when a scanner enters these states.
   *     [["string", "code"], '[^"]',  "return 'STRING';"],
   *   ],
   *
   *   // https://gist.github.com/DmitrySoshnikov/f5e2583b37e8f758c789cea9dcdf238a
   *   "startConditions": {
   *     "string": 1, // inclusive condition %s
   *     "code": 0,   // exclusive consition %x
   *   },
   * }
   */
  function LexGrammar(_ref) {
    var macros = _ref.macros,
        rules = _ref.rules,
        startConditions = _ref.startConditions,
        options = _ref.options;

    _classCallCheck(this, LexGrammar);

    this._macros = macros;
    this._originalRules = rules;
    this._options = options;
    this._extractMacros(macros, this._originalRules);

    this._rules = this._processRules(this._originalRules);
    this._rulesToIndexMap = this._createRulesToIndexMap();

    this._startConditions = Object.assign({ INITIAL: 0 }, startConditions);
    this._rulesByStartConditions = this._processRulesByStartConditions();
  }

  /**
   * Returns options.
   */


  _createClass(LexGrammar, [{
    key: 'getOptions',
    value: function getOptions() {
      return this._options;
    }

    /**
     * Returns start conditions types for a lexer.
     */

  }, {
    key: 'getStartConditions',
    value: function getStartConditions() {
      return this._startConditions;
    }

    /**
     * Returns lexical rules.
     */

  }, {
    key: 'getRules',
    value: function getRules() {
      return this._rules;
    }

    /**
     * Returns a rule by index.
     */

  }, {
    key: 'getRuleByIndex',
    value: function getRuleByIndex(index) {
      return this._rules[index];
    }

    /**
     * Returns rule's index.
     */

  }, {
    key: 'getRuleIndex',
    value: function getRuleIndex(rule) {
      return this._rulesToIndexMap.get(rule);
    }

    /**
     * Returns original lexical rules data.
     */

  }, {
    key: 'getOriginalRules',
    value: function getOriginalRules() {
      return this._originalRules;
    }

    /**
     * Returns macros.
     */

  }, {
    key: 'getMacros',
    value: function getMacros() {
      return this._macros;
    }

    /**
     * Returns lexical rules for a specific start condition.
     */

  }, {
    key: 'getRulesForState',
    value: function getRulesForState(state) {
      return this._rulesByStartConditions[state];
    }

    /**
     * Returns rules by start conditions.
     */

  }, {
    key: 'getRulesByStartConditions',
    value: function getRulesByStartConditions() {
      return this._rulesByStartConditions;
    }

    /**
     * Creates rules to index map.
     */

  }, {
    key: '_createRulesToIndexMap',
    value: function _createRulesToIndexMap() {
      var rulesToIndexMap = new Map();
      this.getRules().forEach(function (rule, index) {
        rulesToIndexMap.set(rule, index);
      });
      return rulesToIndexMap;
    }

    /**
     * Processes lexical rules data, creating `LexRule` instances for each.
     */

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

      return rules.map(function (tokenData) {
        // Lex rules may specify start conditions. Such rules are
        // executed if a tokenizer enters such state.

        var startConditions = void 0;
        var matcher = void 0;
        var tokenHandler = void 0;
        var options = {};

        // Default options of a particular LexRule are initialized to the
        // global options of the whole lexical grammar.
        var defaultOptions = Object.assign({}, _this.getOptions());

        if (tokenData.length === 2) {
          var _tokenData = _slicedToArray(tokenData, 2);

          matcher = _tokenData[0];
          tokenHandler = _tokenData[1];
        } else if (tokenData.length === 3) {
          // Start conditions, no options.
          if (Array.isArray(tokenData[0]) && typeof tokenData[2] === 'string') {
            var _tokenData2 = _slicedToArray(tokenData, 3);

            startConditions = _tokenData2[0];
            matcher = _tokenData2[1];
            tokenHandler = _tokenData2[2];
          }

          // Trailing options, no start conditions.
          else if (typeof tokenData[0] === 'string' && typeof tokenData[2] === 'object') {
              var _tokenData3 = _slicedToArray(tokenData, 3);

              matcher = _tokenData3[0];
              tokenHandler = _tokenData3[1];
              options = _tokenData3[2];
            }
        } else if (tokenData.length === 4) {
          var _tokenData4 = _slicedToArray(tokenData, 4);

          startConditions = _tokenData4[0];
          matcher = _tokenData4[1];
          tokenHandler = _tokenData4[2];
          options = _tokenData4[3];
        }

        return new _lexRule2.default({
          startConditions: startConditions,
          matcher: matcher,
          tokenHandler: tokenHandler,
          options: Object.assign(defaultOptions, options)
        });
      });
    }

    /**
     * Builds a map from a start condition to a list of
     * lex rules which should be executed once a lexer
     * enters this state.
     */

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

      var rulesByConditions = {};

      var _loop = function _loop(condition) {
        var inclusive = _this2._startConditions[condition] === 0;

        var rules = _this2._rules.filter(function (lexRule) {
          // A rule is included if a lexer is in this state,
          // or if a condition is inclusive, and a rule doesn't have
          // any explicit start conditions. Also if the condition is `*`.
          // https://gist.github.com/DmitrySoshnikov/f5e2583b37e8f758c789cea9dcdf238a
          return inclusive && !lexRule.hasStartConditions() || lexRule.hasStartConditions() && (lexRule.getStartConditions().indexOf(condition) !== -1 || lexRule.getStartConditions().indexOf('*') !== -1);
        });

        rulesByConditions[condition] = rules;
      };

      for (var condition in this._startConditions) {
        _loop(condition);
      }

      return rulesByConditions;
    }

    /**
     * If lexical grammar provides "macros" property, and has e.g entry:
     * "digit": "[0-9]", with later usage of {digit} in the lex rules,
     * this functions expands it to [0-9].
     */

  }, {
    key: '_extractMacros',
    value: function _extractMacros(macros, rules) {
      rules.forEach(function (lexData) {
        var index = lexData.length === 3 ? 1 : 0;

        // Standard macros.

        var _loop2 = function _loop2(macro) {
          if (lexData[index].indexOf(macro) !== -1) {
            lexData[index] = lexData[index].replace(new RegExp(macro, 'g'), function () {
              return StandardMacros[macro];
            });
          }
        };

        for (var macro in StandardMacros) {
          _loop2(macro);
        }

        if (!macros) {
          return;
        }

        var _loop3 = function _loop3(macro) {
          // User-level macros.
          if (lexData[index].indexOf('{' + macro + '}') !== -1) {
            lexData[index] = lexData[index].replace(new RegExp('\\{' + macro + '\\}', 'g'), function () {
              return macros[macro];
            });
          }
        };

        for (var macro in macros) {
          _loop3(macro);
        }
      });
    }
  }]);

  return LexGrammar;
}();

exports.default = LexGrammar;