'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 _specialSymbols = require('./special-symbols');

var _debug = require('./debug');

var _debug2 = _interopRequireDefault(_debug);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

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

/**
 * Base parser generator for LL and LR.
 */
var BaseParserGenerator = function () {
  /**
   * Instance constructor.
   */
  function BaseParserGenerator(_ref) {
    var grammar = _ref.grammar,
        outputFile = _ref.outputFile,
        customTokenizer = _ref.customTokenizer,
        _ref$options = _ref.options,
        options = _ref$options === undefined ? {} : _ref$options;

    _classCallCheck(this, BaseParserGenerator);

    _debug2.default.time('Generating parser module');

    this._grammar = grammar;
    this._outputFile = outputFile;
    this._customTokenizer = customTokenizer;
    this._encodeSymbols();
    this._options = options;

    var templateTags = this.getTemplateTags();
    this._openTag = templateTags.open;
    this._closeTag = templateTags.close;
  }

  /**
   * Returns template tags.
   */


  _createClass(BaseParserGenerator, [{
    key: 'getTemplateTags',
    value: function getTemplateTags() {
      // Default template tags, child classes may override.
      return {
        open: '{{{',
        close: '}}}'
      };
    }

    /**
     * Returns options.
     */

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

    /**
     * Initializes result data from the template.
     */

  }, {
    key: 'setTemplate',
    value: function setTemplate(template) {
      this._resultData = template;
      return this;
    }

    /**
     * Sets the parsing table.
     */

  }, {
    key: 'setTable',
    value: function setTable(table) {
      this._table = table;
      return this;
    }

    /**
     * Returns the parsing table.
     */

  }, {
    key: 'getTable',
    value: function getTable() {
      return this._table;
    }

    /**
     * Returns grammar.
     */

  }, {
    key: 'getGrammar',
    value: function getGrammar() {
      return this._grammar;
    }

    /**
     * Generates code for semantic action.
     */

  }, {
    key: 'buildSemanticAction',
    value: function buildSemanticAction(production) {
      var semanticActionCode = this.getSemanticActionCode(production);

      if (!semanticActionCode) {
        return null;
      }

      var semanticActionArgs = this.getSemanticActionParams(production).join(',');

      return '(' + semanticActionArgs + ') => { ' + semanticActionCode + ' }';
    }

    /**
     * Creates handler prologue for locations. Use default implementation
     * from CodeUnit, plugins may implement custom logic.
     */

  }, {
    key: 'createLocationPrologue',
    value: function createLocationPrologue(production) {
      return _codeUnit2.default.createLocationPrologue(production);
    }

    /**
     * Returns a list of semantic action parameters. Plugins
     * can transform it, e.g. adding type information.
     */

  }, {
    key: 'getSemanticActionParams',
    value: function getSemanticActionParams(production) {
      return _codeUnit2.default.createProductionParamsArray({
        production: production,
        captureLocations: this._grammar.shouldCaptureLocations()
      });
    }

    /**
     * Returns transformed semantic action code.
     */

  }, {
    key: 'getSemanticActionCode',
    value: function getSemanticActionCode(production) {
      var rawAction = production.getRawSemanticAction();

      if (!rawAction) {
        return null;
      }

      var action = rawAction
      // Replace $1, $2, @1, ... $$ with _1, _2, _1loc, ... __, etc.
      .replace(/\$(\d+)/g, '_$1').replace(/@(\d+)/g, '_$1loc').replace(/\$\$/g, '__').replace(/@\$/g, '__loc');

      if (this._grammar.shouldCaptureLocations()) {
        action = this.createLocationPrologue(production) + action;
      }

      return action || null;
    }

    /**
     * Generates parser code and writes it to disk as a reusable module.
     */

  }, {
    key: 'generate',
    value: function generate() {
      this.generateParserData();
      if (!this._outputFile) {
        return this._resultData;
      }
      _fs2.default.writeFileSync(this._outputFile, this._resultData, 'utf-8');
      _debug2.default.timeEnd('Generating parser module');
      try {
        return require(this._outputFile);
      } catch (e) {
        /* skip for other languages */
      }
    }
  }, {
    key: 'getEncodedToken',
    value: function getEncodedToken(token) {
      if (!this._tokens.hasOwnProperty(token)) {
        return -1;
      }
      return this._tokens[token];
    }
  }, {
    key: 'getEncodedNonTerminal',
    value: function getEncodedNonTerminal(nonTerminal) {
      if (!this._nonTerminals.hasOwnProperty(nonTerminal)) {
        return -1;
      }
      return this._nonTerminals[nonTerminal];
    }
  }, {
    key: 'getEncodedSymbol',
    value: function getEncodedSymbol(symbol) {
      var nonTerminal = this.getEncodedNonTerminal(symbol);

      if (nonTerminal !== -1) {
        return nonTerminal;
      }

      return this.getEncodedToken(symbol);
    }

    /**
     * Writes data for a given template variable.
     */

  }, {
    key: 'writeData',
    value: function writeData(templateVariable, data) {
      this._resultData = this._resultData.replace(this._openTag + templateVariable + this._closeTag, function () {
        return data;
      });
      return this;
    }

    /**
     * Generates a wrapping namespace.
     */

  }, {
    key: 'generateNamespace',
    value: function generateNamespace() {}
    /* no-op, plugins can override */


    /**
     * Generates parser parts.
     */

  }, {
    key: 'generateParserData',
    value: function generateParserData() {
      // Generate a wrapping namespace.
      this.generateNamespace();

      // Arbitrary code included to the module.
      this.generateModuleInclude();

      // Whether locations should be captured, and propagated.
      this.generateCaptureLocations();

      // Lexical grammar.
      this.generateTokenizer();

      // Syntactic grammar.
      this.generateProductions();

      // Tables.
      this.generateTokensTable();
      this.generateParseTable();

      return this._resultData;
    }

    /**
     * Whether locations should be captured, and propagated.
     */

  }, {
    key: 'generateCaptureLocations',
    value: function generateCaptureLocations() {
      this.writeData('CAPTURE_LOCATIONS', JSON.stringify(this._grammar.shouldCaptureLocations()));
    }

    /**
     * Encodes tokens, and non-terminals as indices (starting with
     * non-terminals in order, then tokens).
     */

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

      var index = 0;

      this._nonTerminals = {};
      this._grammar.getNonTerminals().forEach(function (symbol) {
        return _this._nonTerminals[symbol.getSymbol()] = '' + index++;
      });

      this._tokens = {};
      this._grammar.getTokens().concat(this._grammar.getTerminals()).forEach(function (symbol) {
        return _this._tokens[symbol.getSymbol()] = '' + index++;
      });

      this._tokens[_specialSymbols.EOF] = '' + index;
    }

    /**
     * Generates code for a built-in or a custom tokenizer.
     */

  }, {
    key: 'generateTokenizer',
    value: function generateTokenizer() {
      if (!this._customTokenizer) {
        // Built-in tokenizer.
        this.generateBuiltInTokenizer();
        this.generateLexRules();
        this.generateLexRulesByStartConditions();
      } else {
        // Require custom tokenizer if was provided.
        this.writeData('TOKENIZER', 'tokenizer = require(\'' + this._customTokenizer + '\');');
      }
    }

    /**
     * Injects the code passed in the module include directive.
     */

  }, {
    key: 'generateModuleInclude',
    value: function generateModuleInclude() {
      this.writeData('MODULE_INCLUDE', this._grammar.getModuleInclude());
    }

    /**
     * Generates built-in tokenizer instance.
     */

  }, {
    key: 'generateBuiltInTokenizer',
    value: function generateBuiltInTokenizer() {
      var tokenizerCode = _fs2.default.readFileSync(__dirname + '/./templates/tokenizer.template.js', 'utf-8');
      this.writeData('TOKENIZER', tokenizerCode);
    }

    /**
     * Generates rules for tokenizer.
     */

  }, {
    key: 'generateLexRules',
    value: function generateLexRules() {
      var lexRules = this._grammar.getLexGrammar().getRules().map(function (lexRule) {
        return '[' + lexRule.getMatcher() + ', ' + ('function() { ' + lexRule.getRawHandler() + ' }]');
      });

      this.writeData('LEX_RULES', '[' + lexRules.join(',\n') + ']');
    }

    /**
     * Generates lex rule table by start conditions.
     * conditionName: [<ruleIndex in the LEX_RULES table>, ...]
     */

  }, {
    key: 'generateLexRulesByStartConditions',
    value: function generateLexRulesByStartConditions() {
      var lexGrammar = this._grammar.getLexGrammar();
      var lexRulesByConditions = lexGrammar.getRulesByStartConditions();
      var result = {};

      for (var condition in lexRulesByConditions) {
        result[condition] = lexRulesByConditions[condition].map(function (lexRule) {
          return lexGrammar.getRuleIndex(lexRule);
        });
      }

      this.writeData('LEX_RULES_BY_START_CONDITIONS', '' + JSON.stringify(result));
    }
  }, {
    key: 'generateProductions',
    value: function generateProductions() {
      this.writeData('PRODUCTIONS', '[' + this.generateProductionsData().join(',\n') + ']');
    }

    /**
     * Abstract method to implement in LL/LR.
     */

  }, {
    key: 'generateProductionsData',
    value: function generateProductionsData() {
      throw new Error('Parser generator: `generateProductionsData` is not implemented.');
    }
  }, {
    key: 'generateTokensTable',
    value: function generateTokensTable() {
      this.writeData('TOKENS', JSON.stringify(this._tokens));
    }

    /**
     * Actual parsing table.
     */

  }, {
    key: 'generateParseTable',
    value: function generateParseTable() {
      this.writeData('TABLE', JSON.stringify(this.generateParseTableData()));
    }

    /**
     * Abstract method to implement in LL/LR.
     */

  }, {
    key: 'generateParseTableData',
    value: function generateParseTableData() {
      throw new Error('Parser generator: `generateParseTableData` is not implemented.');
    }
  }]);

  return BaseParserGenerator;
}();

exports.default = BaseParserGenerator;