'use strict';

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _colors = require('colors');

var _colors2 = _interopRequireDefault(_colors);

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

/**
 * Rust tokenizer template.
 */
/**
 * The MIT License (MIT)
 * Copyright (c) 2015-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
 */

var RUST_TOKENIZER_TEMPLATE = _fs2.default.readFileSync(__dirname + '/templates/tokenizer.template.rs', 'utf-8');

/**
 * Default error handler for rust parser when encountered an error. 
 */
var DEFAULT_ERROR_HANDLER = '\n  if token.value == EOF && !self.tokenizer.has_more_tokens() {\n    panic!("Unexpected end of input.");\n  }\n  self.tokenizer.panic_unexpected_token(token.value, token.start_line, token.start_column);\n';

/**
 * Make replacer for String#replace method which replace match with replaceText only if first capture group is not '.'
 * RegExp Lookbehind is not supported in node version less than 10.x
 * @param {string} replaceText
 */
function ifNotStartsWithDotReplacer(replaceText) {
  return function (m, maybeDot) {
    if (maybeDot === '.') {
      return m;
    }
    return maybeDot + replaceText;
  };
}

/**
 * The trait is used by parser generators (LL/LR) for Rust.
 */
var RustParserGeneratorTrait = {

  /**
   * Generates parsing table in Rust vector format.
   */
  generateParseTable: function generateParseTable() {
    this.writeData('TABLE', this._buildTable(this.generateParseTableData()));
  },


  /**
   * Converts JS object into Rust HashMap.
   *
   * In Rust we represent a table as a vector, where index is a state number,
   * and a value is a hashmap of LR entries (shift/reduce/etc).
   *
   * Example:
   *
   * vec![
   *     hashmap! { 1 => TE::Shift(4), 3 => TE::Reduce(1), ... },
   * ]
   */
  _buildTable: function _buildTable(table) {
    var _this = this;

    var entries = Object.keys(table).map(function (state) {
      var row = table[state];

      // Transform to Rust enum format: "s3" => TE::Shift(3), etc
      Object.keys(row).forEach(function (key) {
        var entry = row[key];
        if (entry[0] === 's') {
          row[key] = 'TE::Shift(' + entry.slice(1) + ')';
        } else if (entry[0] === 'r') {
          row[key] = 'TE::Reduce(' + entry.slice(1) + ')';
        } else if (entry === 'acc') {
          row[key] = 'TE::Accept';
        } else {
          row[key] = 'TE::Transit(' + entry + ')';
        }
      });

      return _this._toRustHashMap(table[state], 'number');
    });

    return 'vec![\n    ' + entries.join(',\n    ') + '\n]';
  },


  /**
   * Generates tokens table in Rust hashmap format.
   */
  generateTokensTable: function generateTokensTable() {
    this.writeData('TOKENS', this._toRustHashMap(this._tokens, 'string', 'number'));
  },


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

    var _extractDataTypes = this._extractDataTypes(originalAction),
        action = _extractDataTypes.action,
        types = _extractDataTypes.types;

    action = this._actionFromHandler(action, '.tokenizer');

    action = this._generateArgsPrologue(action, types,
    // Total number of args.
    production.isEpsilon() ? 0 : production.getRHS().length);

    // Append return value.
    var returnValue = types.hasOwnProperty('__') ? 'SV::_' + this._allTypes[types.__] + '(__)' : '__';

    action = action + ('\n' + returnValue);

    // Save the action, they are injected later.
    this._productionHandlers.push({ args: '&mut self', action: action });
    return null;
  },


  /**
   * Builds SV (stack value) enum from all the used types in handlers.
   */
  generateStackValueEnum: function generateStackValueEnum() {
    var svEnum = Object.keys(this._allTypes).map(function (typeName, idx) {
      return '_' + idx + '(' + typeName + ')';
    });
    this.writeData('SV_ENUM', svEnum.join(',\n    '));
  },


  /**
   * Generates prologue for fetching arguments from the parsing stack.
   */
  _generateArgsPrologue: function _generateArgsPrologue(action, types, totalArgsCount) {
    var argsPrologue = [];

    for (var i = totalArgsCount; i > 0; i--) {
      var arg = '_' + i;

      if (!types.hasOwnProperty(arg)) {
        // Just pop if arg is not used in the handler.
        argsPrologue.push('self.values_stack.pop();');
      } else {
        var typeInfo = types[arg];

        if (typeInfo) {
          argsPrologue.push('let mut ' + arg + ' = pop!(self.values_stack, ' + ('_' + this._allTypes[typeInfo] + ');'));
        } else {
          argsPrologue.push('let mut ' + arg + ' = self.values_stack.pop().unwrap();');
        }
      }
    }

    return '// Semantic values prologue.\n' + argsPrologue.join('\n') + '\n\n' + action;
  },


  /**
   * Extracts types of the used arguments, and return types.
   * If a type is defined, a popped stack value is casted,
   * otherwise, it's just popped (if doesn't participate in an operation
   * and just propagated).
   */
  _extractDataTypes: function _extractDataTypes(action) {
    var _this2 = this;

    var types = {};

    if (!action) {
      return { action: '', types: types };
    }

    var typesRe = /\s*\|([^|]*)\|\s*->\s*([\w&][\w<>, '&]*);/g;
    var typesData = typesRe.exec(action);

    if (typesData) {
      // '$1:i32, $3:i32'
      if (typesData[1].trim().length > 0) {
        var argTypes = typesData[1].trim().split(/\s*[,;]\s*/);
        argTypes.forEach(function (argData) {
          var data = argData.split(/\s*:\s*/);
          // types[$1] = 'i32';
          types[data[0]] = data[1];

          // Save the type to all types data to generate enum.
          if (!_this2._allTypes.hasOwnProperty(data[1])) {
            _this2._allTypes[data[1]] = _this2._allTypesIndex++;
          }
        });
      }

      // Result type.
      if (typesData[2]) {
        types.__ = typesData[2];

        if (!this._allTypes.hasOwnProperty(typesData[2])) {
          this._allTypes[typesData[2]] = this._allTypesIndex++;
        }
      }
    }

    // Strip the types info.
    action = action.replace(typesRe, '');

    // Extract other args, which do not use types.
    var argsRe = /_\d+/g;

    var usedArgs = action.match(argsRe);
    if (usedArgs) {
      usedArgs.forEach(function (usedArg) {
        if (!types.hasOwnProperty(usedArg)) {
          // Arg is used, but without a type.
          types[usedArg] = null;
        }
      });
    }

    return { types: types, action: action };
  },


  /**
   * Default format in the [ ] array notation.
   */
  generateProductionsData: function generateProductionsData() {
    return this.generateRawProductionsData().map(function (data) {
      // Remove the semantic action handler, since in Rust
      // we use a different structure to hold it.
      delete data[2];
      return '[' + data.join(', ') + ']';
    });
  },


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


  /**
   * Creates an action from raw handler.
   */
  _actionFromHandler: function _actionFromHandler(handler) {
    var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

    var action = (this._scopeVars(handler, context) || '').trim();

    if (!action) {
      return 'let __ = SV::Undefined;';
    }

    // From parser hooks, append ; at the end.
    if (context === '.tokenizer' && !/;\s*$/.test(action)) {
      action += ';';
    }

    return action;
  },


  /**
   * Generates rules for tokenizer.
   */
  generateLexRules: function generateLexRules() {
    var _this3 = this;

    var lexRulesArray = [];

    var lexRules = this._grammar.getLexGrammar().getRules().map(function (rule, i) {
      var action = _this3._actionFromHandler(rule.getRawHandler());

      _this3._lexHandlers.push({ args: '&mut self', action: action });

      var flags = [];

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

      if (flags.length > 0) {
        flags = '(?' + flags.join('') + ')';
      } else {
        flags = '';
      }

      lexRulesArray.push('Tokenizer::_lex_rule' + i);

      var matcher = rule.getRawMatcher();

      // there is no need for escape sequence for character '/'
      // (actually you cannot have such escape sequence as '\/', whihc causes rust's regex parser to panic!)
      matcher = matcher.replace("\\/", "/");

      // well, maybe so many # is enough
      // this is for handling character '"' correctly
      // reference: rust's raw string literals https://rahul-thakoor.github.io/rust-raw-string-literals
      return 'r##########"' + flags + matcher + '"##########';
    });

    this.writeData('LEX_RULE_HANDLERS_COUNT', lexRules.length);
    this.writeData('LEX_RULE_HANDLERS_ARRAY', '[\n    ' + lexRulesArray.join(',\n    ') + '\n],');

    this.writeData('LEX_RULES', '[&\'static str; ' + lexRules.length + '] = ' + ('[\n    ' + lexRules.join(',\n    ') + '\n]'));
  },
  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._toRustHashMap(result, 'string'));
  },


  /**
   * Replaces global vars like `yytext`, `$$`, etc. to be
   * referred from `yyparse`.
   */
  _scopeVars: function _scopeVars(code) {
    var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';

    return code.replace(/(.?)yytext/g, ifNotStartsWithDotReplacer('self' + context + '.yytext')).replace(/(.?)yyleng/g, ifNotStartsWithDotReplacer('self' + context + '.yyleng')).replace(/__\s*=/g, 'let __ =').replace(/(.?)yyloc/g, ifNotStartsWithDotReplacer('Loc::from_tokens_range'));
  },


  /**
   * Type-converts a key of a Rust HashMap, e.g. string or number, etc.
   */
  _hashKey: function _hashKey(key, keyType) {
    switch (keyType) {
      case 'string':
        return '"' + key + '"';
      case 'number':
        return Number(key);
      default:
        throw new Error('_hashKey: Incorrect type ' + keyType);
    }
  },


  /**
   * Type-converts a value of a Rust hashmap, e.g. string or number, etc.
   */
  _hashValue: function _hashValue(value, valueType) {
    if (Array.isArray(value)) {
      // Support only int arrays here for simplicity.
      return 'vec! [ ' + value.join(', ') + ' ]';
    }

    switch (valueType) {
      case 'string':
        return '"' + value + '"';
      case 'number':
        return Number(value);
      default:
        return value;
    }
  },


  /**
   * Converts JS object to Rust HashMap representation.
   */
  _toRustHashMap: function _toRustHashMap(object, keyType, valueType) {
    var result = [];
    for (var k in object) {
      var value = object[k];
      var key = k.replace(/"/g, '\\"');
      result.push(this._hashKey(key, keyType) + ' => ' + ('' + this._hashValue(value, valueType)));
    }
    return 'hashmap! { ' + result.join(', ') + ' }';
  },


  /**
   * Rust-specific lex rules handler declarations.
   */
  generateLexHandlers: function generateLexHandlers() {
    var handlers = this._generateHandlers(this._lexHandlers, '_lex_rule', "&'static str");
    this.writeData('LEX_RULE_HANDLERS', handlers.join('\n\n'));
  },


  /**
   * Rust-specific handler declarations.
   */
  generateProductionHandlers: function generateProductionHandlers() {
    var handlers = this._generateHandlers(this._productionHandlers, '_handler', 'SV');

    this.writeData('PRODUCTION_HANDLERS_COUNT', handlers.length);

    var handlersArray = [];
    for (var i = 0; i < handlers.length; i++) {
      handlersArray.push('Parser::_handler' + i);
    }

    this.writeData('PRODUCTION_HANDLERS_ARRAY', '[\n    ' + handlersArray.join(',\n    ') + '\n],');

    this.writeData('PRODUCTION_HANDLERS', handlers.join('\n\n'));
  },


  /**
   * Productions array in Rust format.
   */
  generateProductions: function generateProductions() {
    var productionsData = this.generateProductionsData();
    var productionsCount = productionsData.length;
    this.writeData('PRODUCTIONS', '[[i32; 2]; ' + productionsCount + '] = ' + ('[\n    ' + productionsData.join(',\n    ') + '\n]'));
  },


  /**
   * Module include.
   */
  generateModuleInclude: function generateModuleInclude() {
    var moduleInclude = this._grammar.getModuleInclude();

    var resultTypeData = /type\s+TResult\s*=\s*([^;]+);/.exec(moduleInclude);

    if (!resultTypeData) {
      throw new Error('\n\nRust plugin should provide module include, and define at least ' + ('result type:\n\n  ' + _colors2.default.bold('type TResult = <...>;\n')));
    }

    // Result type.
    var resultType = resultTypeData[1];

    if (!this._allTypes.hasOwnProperty(resultType)) {
      throw new Error('Result type ' + _colors2.default.bold(resultType) + ' is not found in ' + 'handled types. Make sure your productions return it.\n');
    }

    this.writeData('RESULT_TYPE', '_' + this._allTypes[resultType]);

    // Parser hooks.
    var onParseBegin = moduleInclude.indexOf('fn on_parse_begin') !== -1 ? 'on_parse_begin(self, &string);' : '';

    var onParseEnd = moduleInclude.indexOf('fn on_parse_end') !== -1 ? 'on_parse_end(self, &result);' : '';

    var onParseError = moduleInclude.indexOf('fn on_parse_error') !== -1 ? 'on_parse_error(self, &token);' : DEFAULT_ERROR_HANDLER;

    this.writeData('ON_PARSE_BEGIN_CALL', onParseBegin);
    this.writeData('ON_PARSE_END_CALL', onParseEnd);
    this.writeData('ON_PARSE_ERROR_CALL', onParseError);

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


  /**
   * Generates Rust function declarations for handlers.
   */
  _generateHandlers: function _generateHandlers(handlers, name, returnType) {
    return handlers.map(function (_ref, index) {
      var args = _ref.args,
          action = _ref.action;

      return 'fn ' + name + index + ('(' + args + ') -> ' + returnType + ' {\n' + action + '\n}');
    });
  }
};

module.exports = RustParserGeneratorTrait;