'use strict';

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * Example tokenizer template.
 */
var EXAMPLE_TOKENIZER_TEMPLATE = _fs2.default.readFileSync(__dirname + '/templates/tokenizer.template.example', 'utf-8');

/**
 * The trait is used by parser generators (LL/LR) for Example language.
 *
 * Here we can override any needed method from the `LRParserGeneratorDefault`,
 * or even from the very base `BaseParserGenerator` to generate code in a
 * needed target language format.
 *
 * E.g., overriding `generateParseTable()` we can transform the parsing table,
 * which is received from the `generateParseTableData()`, from the JS format
 * to the format of the target language.
 */
/**
 * The MIT License (MIT)
 * Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
 */

var ExampleParserGeneratorTrait = {

  /**
   * Generates parser class name.
   */
  generateParserClassName: function generateParserClassName(className) {
    this.writeData('PARSER_CLASS_NAME', className);
  },


  /**
   * Generates parsing table in Example language Map format.
   *
   * An array of records, where index is a state number, and a value is a
   * map from an encoded symbol (number) to a parsing action.
   *
   * The parsing action can be "Shift/s", "Reduce/r", a state
   * transition number, or "Accept/acc".
   *
   * Example:
   *
   * [
   *   // 0
   *   {
   *     0: "1",
   *     3: "s8",
   *     4: "s2",
   *   },
   *   // 1
   *   {
   *     1: "s3",
   *     2: "s4",
   *     6: "acc",
   *   },
   *   ...
   * ]
   */
  generateParseTable: function generateParseTable() {
    this.writeData('TABLE', this._toExampleLanguageMap(this.generateParseTableData()));
  },


  /**
   * Generates tokens table in Example language Map format.
   */
  generateTokensTable: function generateTokensTable() {
    this.writeData('TOKENS', this._toExampleLanguageMap(this._tokens));
  },


  /**
   * Production handlers are implemented as methods on the `yyparse` class.
   */
  buildSemanticAction: function buildSemanticAction(production) {
    var action = this.getSemanticActionCode(production);

    if (!action) {
      return null;
    }

    // Here can do any transformation on the semantic action.
    action += ';';

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

    // Save the action, they are injected later.
    // "_handler1", "_handler2", etc.
    this._productionHandlers.push({ args: args, action: action });
    return '_handler' + this._productionHandlers.length;
  },


  /**
   * Default format in the [ ] array notation.
   *
   * The `generateRawProductionsData` from the base class returns
   * an encoded grammar productions table.
   *
   * Format of an array:
   *
   * [ <Non-Terminal Index>, <RHS.Length>, <handler> ]
   *
   * Non-terminal indices are from 0 to Last Non-terminal index.
   * LR-algorithm uses length of RHS to pop symbols from the stack; this
   * length is stored as the second element of a record. The last element is
   * an optional name of the semantic action handler. The first record is always
   * a special marker [-1, -1] entry representing an augmented production.
   *
   * Example:
   *
   * [
   *     [-1, 1],
   *     [0, 3, "_handler1"],
   *     [0, 2, "_handler2"],
   *     ...
   * ]
   */
  generateProductionsData: function generateProductionsData() {
    return this.generateRawProductionsData().map(function (data) {
      return JSON.stringify(data);
    });
  },


  /**
   * Generates built-in tokenizer instance.
   */
  generateBuiltInTokenizer: function generateBuiltInTokenizer() {
    this.writeData('TOKENIZER', EXAMPLE_TOKENIZER_TEMPLATE);
  },


  /**
   * Generates rules for tokenizer.
   *
   * Array of arrays:
   *
   * [
   *   [/^\s+/, "_lexRule1"],
   *   [/^\d+/, "_lexRule1"],
   *   ...
   * ]
   *
   */
  generateLexRules: function generateLexRules() {
    var _this = this;

    var lexRules = this._grammar.getLexGrammar().getRules().map(function (lexRule) {

      // Here you may transform the handler code as needed. E.g. if your
      // language doesn't support module-level global variables, such as
      // `yytext`, and `yyleng` which we use as real globals here, you may
      // choose to transform them to some scoped access: you may store
      // `yytext` as a class property (e.g. on yyparse class), and replace
      // `yytext` to `yyparse.yytext` in the code.
      var action = lexRule.getRawHandler() + ';';

      _this._lexHandlers.push({ args: '', action: action });

      var flags = [];

      if (lexRule.isCaseInsensitive()) {
        flags.push('i');
      }

      // Example: ["^\s+", "_lexRule1"],
      return '[/' + lexRule.getRawMatcher() + '/' + flags.join('') + ', ' + ('"_lexRule' + _this._lexHandlers.length + '"]');
    });

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


  /**
   * Generates lexical rules grouped by start conditions. A map from state
   * name to the list of lex rule indices from the `LEX_RULES`.
   *
   * Example:
   *
   * {
   *   "INITIAL": [0,5,6,7],
   *   "comment": [1,2,3,4,6],
   *   ...
   * };
   */
  generateLexRulesByStartConditions: 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', '' + this._toExampleLanguageMap(result));
  },


  /**
   * An imaginary converter from JS format of an object, to the Example
   * language map object. E.g. here you may convert from JS's {foo: 10, bar: 20}
   * into PHP's array('foo' => 10, 'bar' => 20), etc.
   */
  _toExampleLanguageMap: function _toExampleLanguageMap(object) {
    // See other plugin examples to see actual transformation.
    // Here just use the simplest JSON.stringify.
    return JSON.stringify(object);
  },


  /**
   * Example language specific lex rules handler declarations.
   */
  generateLexHandlers: function generateLexHandlers() {
    var handlers = this._generateHandlers(this._lexHandlers, '_lexRule', '' /* return type, you can use e.g. 'string' */
    );
    this.writeData('LEX_RULE_HANDLERS', handlers.join('\n\n'));
  },


  /**
   * Example language specific handler declarations.
   */
  generateProductionHandlers: function generateProductionHandlers() {
    var handlers = this._generateHandlers(this._productionHandlers, '_handler', '' /* return type */
    );
    this.writeData('PRODUCTION_HANDLERS', handlers.join('\n\n'));
  },


  /**
   * Productions array in the Example language format.
   *
   * An array of arrays, see `generateProductionsData` for details.
   */
  generateProductions: function generateProductions() {
    this.writeData('PRODUCTIONS', '[ ' + this.generateProductionsData().join(',\n') + ' ]');
  },


  /**
   * Injects the code passed in the module include directive.
   */
  generateModuleInclude: function generateModuleInclude() {
    var moduleInclude = this._grammar.getModuleInclude();

    if (!moduleInclude) {
      // Example: add some default module include if needed.
      moduleInclude = '\n        let foo = \'Example module include\';\n      ';
    }

    this.writeData('MODULE_INCLUDE', moduleInclude);
  },


  /**
   * Generates function declarations for handlers.
   *
   * Example:
   *
   * _handler1(_1, _2) {
   *   __ = _1 + _2;
   * }
   *
   * Or:
   *
   * _lexRule1() {
   *   return "NUMBER";
   * }
   */
  _generateHandlers: function _generateHandlers(handlers, name) {
    var returnType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';

    return handlers.map(function (_ref, index) {
      var args = _ref.args,
          action = _ref.action;

      return returnType + ' ' + name + (index + 1) + ('(' + args + ') {\n' + action + '\n}');
    });
  }
};

module.exports = ExampleParserGeneratorTrait;