'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 _canonicalCollection = require('./canonical-collection');

var _canonicalCollection2 = _interopRequireDefault(_canonicalCollection);

var _lrParsingTable = require('./lr-parsing-table');

var _lrParsingTable2 = _interopRequireDefault(_lrParsingTable);

var _lrParserGeneratorDefault = require('./lr-parser-generator-default');

var _lrParserGeneratorDefault2 = _interopRequireDefault(_lrParserGeneratorDefault);

var _tokenizer = require('../tokenizer');

var _tokenizer2 = _interopRequireDefault(_tokenizer);

var _specialSymbols = require('../special-symbols');

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

var _debug2 = _interopRequireDefault(_debug);

var _os = require('os');

var _os2 = _interopRequireDefault(_os);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

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

var EntryType = _lrParsingTable2.default.EntryType;

var LRParser = function () {
  function LRParser(_ref) {
    var grammar = _ref.grammar,
        parserModule = _ref.parserModule,
        resolveConflicts = _ref.resolveConflicts;

    _classCallCheck(this, LRParser);

    this._grammar = grammar;
    this._parserModule = parserModule;

    this._canonicalCollection = new _canonicalCollection2.default({
      grammar: this._grammar
    });

    this._table = new _lrParsingTable2.default({
      canonicalCollection: this._canonicalCollection,
      grammar: this._grammar,
      resolveConflicts: resolveConflicts
    });

    this._tokenizer = new _tokenizer2.default({
      lexGrammar: this._grammar.getLexGrammar()
    });

    // Parsing stack.
    this._stack = [];

    // Parse object which may define handlers for parse events.
    this._yyparse = _codeUnit2.default.getSandbox().yyparse;

    // Global storage accessible from semantic actions.
    this._yy = _codeUnit2.default.getSandbox().yy;

    // Parse options.
    this._yy.options = {};

    // Parser may access tokenizer, and affect its state.
    this._yy.tokenizer = this._tokenizer;

    // Alias for tokenizer.
    this._yy.lexer = this._tokenizer;

    // Whether locations should be captured.
    this._yy.options.captureLocations = grammar.shouldCaptureLocations();

    // Execute module include code which may attach
    // handlers for some events, and define needed data.
    _codeUnit2.default.eval(this._grammar.getModuleInclude(), /*shouldRewrite*/false);
  }

  _createClass(LRParser, [{
    key: 'getGrammar',
    value: function getGrammar() {
      return this._grammar;
    }
  }, {
    key: 'getTable',
    value: function getTable() {
      return this._table;
    }
  }, {
    key: 'getCanonicalCollection',
    value: function getCanonicalCollection() {
      return this._canonicalCollection;
    }
  }, {
    key: 'parse',
    value: function parse(string) {
      // If parser module has been generated, use it.
      if (this._parserModule) {
        _debug2.default.time('LR parsing from module');

        var value = this._parserModule.parse(string);

        _debug2.default.timeEnd('LR parsing from module');

        return {
          status: 'accept',
          value: value
        };
      }

      _debug2.default.time('LR parsing');

      this._tokenizer.initString(string);

      if (this._yyparse.onParseBegin) {
        this._yyparse.onParseBegin(string, this._tokenizer, this._yy.options);
      }

      this._stack = [];

      var startingState = this._canonicalCollection.getStartingState().getNumber();

      // Start from the initial state.
      this._stack.push(startingState);

      var token = this._tokenizer.getNextToken();
      var shiftedToken = null;

      do {
        if (!token) {
          this._unexpectedEndOfInput();
        }

        var state = this._peek();
        var column = token.type;
        var entry = this._table.get()[state][column];

        if (!entry) {
          this._unexpectedToken(token);
        }

        switch (_lrParsingTable2.default.getEntryType(entry)) {
          case EntryType.SHIFT:
            if (this._yyparse.onShift) {
              token = this._yyparse.onShift(token);
            }
            this._shift(token, entry);
            shiftedToken = token;
            token = this._tokenizer.getNextToken();
            break;
          case EntryType.REDUCE:
            this._reduce(entry, shiftedToken);
            // Don't advance tokens on reduce.
            break;
          case EntryType.SR_CONFLICT:
            this._conflictError('shift-reduce', state, column);
            break;
          case EntryType.RR_CONFLICT:
            this._conflictError('reduce-reduce', state, column);
            break;
          case EntryType.ACCEPT:
            {
              // Pop starting production and its state number.
              this._stack.pop();
              var parsed = this._stack.pop();

              if (this._stack.length !== 1 || this._stack[0] !== startingState || this._tokenizer.hasMoreTokens()) {
                this._unexpectedToken(token);
              }

              var result = { status: 'accept' };

              if (parsed.hasOwnProperty('semanticValue')) {
                result.value = parsed.semanticValue;
              }

              if (this._yyparse.onParseEnd) {
                this._yyparse.onParseEnd(result.value);
              }

              _debug2.default.timeEnd('LR parsing');

              return result;
            }
        }
      } while (this._tokenizer.hasMoreTokens() || this._stack.length > 1);
    }
  }, {
    key: '_unexpectedEndOfInput',
    value: function _unexpectedEndOfInput() {
      this._parseError('Unexpected end of input.');
    }
  }, {
    key: '_unexpectedToken',
    value: function _unexpectedToken(token) {
      if (token.type === _specialSymbols.EOF) {
        this._unexpectedEndOfInput();
      }

      this._tokenizer.throwUnexpectedToken(token.value, token.startLine, token.startColumn);
    }
  }, {
    key: '_parseError',
    value: function _parseError(message) {
      throw new SyntaxError(message);
    }
  }, {
    key: '_conflictError',
    value: function _conflictError(conflictType, state, column) {
      this._parseError('Found "' + conflictType + '" conflict ' + ('at state ' + state + ', terminal ' + column + '.'));
    }
  }, {
    key: '_peek',
    value: function _peek() {
      return this._stack[this._stack.length - 1];
    }
  }, {
    key: '_shift',
    value: function _shift(token, entry) {
      var loc = null;

      if (this._grammar.shouldCaptureLocations()) {
        loc = {
          startOffset: token.startOffset,
          endOffset: token.endOffset,
          startLine: token.startLine,
          endLine: token.endLine,
          startColumn: token.startColumn,
          endColumn: token.endColumn
        };
      }

      this._stack.push({
        symbol: token.type,
        semanticValue: token.value,
        loc: loc
      }, Number(entry.slice(1)));
    }
  }, {
    key: '_reduce',
    value: function _reduce(entry, token) {
      var productionNumber = entry.slice(1);
      var production = this._grammar.getProduction(productionNumber);
      var hasSemanticAction = production.hasSemanticAction();
      var semanticValueArgs = hasSemanticAction ? [] : null;

      var locationArgs = hasSemanticAction && this._grammar.shouldCaptureLocations() ? [] : null;

      // Pop 2x symbols from the stack (RHS + state number for each),
      // unless it's an ε-production for which nothing to pop.
      if (!production.isEpsilon()) {
        var rhsLengh = production.getRHS().length;
        while (rhsLengh--) {
          // Pop state number;
          this._stack.pop();
          // Pop production symbol.
          var stackEntry = this._stack.pop();

          if (hasSemanticAction) {
            semanticValueArgs.unshift(stackEntry.semanticValue);

            if (locationArgs) {
              locationArgs.unshift(stackEntry.loc);
            }
          }
        }
      }

      var previousState = this._peek();
      var symbolToReduceWith = production.getLHS().getSymbol();

      var reduceStackEntry = { symbol: symbolToReduceWith };

      if (hasSemanticAction) {
        _codeUnit2.default.setBindings({
          yytext: token ? token.value : '',
          yyleng: token ? token.value.length : 0
        });

        var semanticActionArgs = locationArgs !== null ? semanticValueArgs.concat(locationArgs) : semanticValueArgs;

        // Run corresponding semantic action, result is in $$ (__).
        production.runSemanticAction(semanticActionArgs);

        reduceStackEntry.semanticValue = _codeUnit2.default.getSandbox().__;

        if (locationArgs) {
          reduceStackEntry.loc = _codeUnit2.default.getSandbox().__loc;
        }
      }

      // Then push LHS.
      this._stack.push(reduceStackEntry);

      var nextState = this._table.get()[previousState][symbolToReduceWith];

      // And push the next state (goto)
      this._stack.push(nextState);
    }
  }], [{
    key: 'fromParserGenerator',
    value: function fromParserGenerator(_ref2) {
      var grammar = _ref2.grammar,
          options = _ref2.options;

      // Generate parser in the temp directory.
      var outputFile = _path2.default.resolve(_os2.default.tmpdir(), '.syntax-parser.js');

      var parserModule = new _lrParserGeneratorDefault2.default({
        grammar: grammar,
        outputFile: outputFile,
        options: options
      }).generate();

      return new LRParser({ grammar: grammar, parserModule: parserModule });
    }
  }]);

  return LRParser;
}();

exports.default = LRParser;