'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);

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

/**
 * A lexical grammar rule.
 */
var LexRule = function () {
  /**
   * Format of a rule is: `<matcher> : <tokenType>`.
   *
   * In the simplest case a terminal matches itself, and the
   * token is also the value of the terminal:
   *
   *   a : "a"
   *
   * In other regexp cases, the token can be a class of
   * tokens, like `NUMBER`:
   *
   *   [0-9]+(\.[0-9]+)? : NUMBER
   *
   * The `startConditions` allows specifying lexer state, and execute
   * a rule only if a tokenizer is within this state.
   *
   * Available options:
   *
   *   - case-insensitive: boolean
   */
  function LexRule(_ref) {
    var startConditions = _ref.startConditions,
        matcher = _ref.matcher,
        tokenHandler = _ref.tokenHandler,
        _ref$options = _ref.options,
        options = _ref$options === undefined ? {} : _ref$options;

    _classCallCheck(this, LexRule);

    this._startConditions = startConditions;
    this._options = options;
    this._originalMatcher = matcher;
    this._rawMatcher = this._buildRawMatcher(this._originalMatcher);
    this._matcher = this._buildMatcher(this._rawMatcher);
    this._rawHandler = tokenHandler;
    this._handler = this._buildHandler(tokenHandler);
  }

  /**
   * Returns options.
   */


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

    /**
     * Whether the rule is case-insensitive.
     */

  }, {
    key: 'isCaseInsensitive',
    value: function isCaseInsensitive() {
      return !!this._options['case-insensitive'];
    }

    /**
     * Retusn original matcher string.
     */

  }, {
    key: 'getOriginalMatcher',
    value: function getOriginalMatcher() {
      return this._originalMatcher;
    }

    /**
     * Retusn raw matcher string.
     */

  }, {
    key: 'getRawMatcher',
    value: function getRawMatcher() {
      return this._rawMatcher;
    }

    /**
     * Returns matcher function.
     */

  }, {
    key: 'getMatcher',
    value: function getMatcher() {
      return this._matcher;
    }

    /**
     * Returns raw handler.
     */

  }, {
    key: 'getRawHandler',
    value: function getRawHandler() {
      return this._rawHandler;
    }

    /**
     * Returns handler.
     */

  }, {
    key: 'getHandler',
    value: function getHandler() {
      return this._handler;
    }

    /**
     * Returns token data.
     */

  }, {
    key: 'getTokenData',
    value: function getTokenData(matched, tokenizer) {
      return this._handler(matched, tokenizer);
    }

    /**
     * Returns start conditions for this rule.
     */

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

    /**
     * Whether this rule has start conditions.
     */

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

    /**
     * Generates a data array like used in lex grammar files
     * from the LexRule instance.
     */

  }, {
    key: 'toData',
    value: function toData() {
      var data = [this.getOriginalMatcher(), this.getRawHandler()];

      if (this.hasStartConditions()) {
        data.unshift(this.getStartConditions());
      }

      return data;
    }

    /**
     * Tokenizer applies all regexp rules from the beginning of the
     * non-analized yet substring (i.e. appending ^ to regexp).
     *
     * E.g. \d+ becomes ^\d+
     *
     * Note: lookbehind assertions should append ^ not at the beginning
     * of the regexp, but at the begininng of the asserting chars:
     *
     * (?<= )a(?= ) becomes (?<=^ )a(?= )
     */

  }, {
    key: '_buildRawMatcher',
    value: function _buildRawMatcher(originalMatcher) {
      var prefix = originalMatcher.substring(0, 4);

      // Positive or negative lookbehind.
      if (prefix === '(?<=' || prefix === '(?<!') {
        return prefix + '^' + originalMatcher.slice(4);
      }

      // Simple ^.
      return '^' + originalMatcher;
    }

    /**
     * Creates an actual regexp object from the regexp string (only for JS target
     * langauges, other plugins use rawMatcher instead ad code generation).
     */

  }, {
    key: '_buildMatcher',
    value: function _buildMatcher(rawMatcher) {
      this._matcher = null;
      try {
        var flags = [];

        if (this._options['case-insensitive']) {
          flags.push('i');
        }

        this._matcher = new RegExp(rawMatcher, flags.join(''));
      } catch (e) {
        /* Skip for other languages */
      }
      return this._matcher;
    }

    /**
     * Builds a wrapper function on top of the handler.
     * This is in order to be able directly modify `yytext`,
     * (which is closured), and return a token.
     */

  }, {
    key: '_buildHandler',
    value: function _buildHandler(tokenHandler) {
      try {
        /* Generate the function handler only for JS language */
        var handler = _codeUnit2.default.createHandler( /* no params */'', tokenHandler);
        return function (matched, tokenizer) {
          _codeUnit2.default.setBindings({
            yytext: matched,
            yyleng: matched.length
          });

          // Call the handler.
          var token = handler.call(tokenizer);
          var yytext = _codeUnit2.default.getSandbox().yytext;

          // Update the `yyleng` in case `yytext` was modified.
          _codeUnit2.default.setBindings({
            yyleng: yytext.length
          });

          // The handler may mutate `yytext` during execution,
          // return a possibly updated one, along with the token.
          return [yytext, token];
        };
      } catch (e) {
        /* And skip for other languages, which use raw handler in generator */
      }
    }
  }], [{
    key: 'matcherFromTerminal',
    value: function matcherFromTerminal(terminal) {
      return terminal.slice(1, -1).replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
    }
  }]);

  return LexRule;
}();

exports.default = LexRule;