'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _vm = require('vm');

var _vm2 = _interopRequireDefault(_vm);

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

var SANDBOX = Object.assign({}, global, {
  /**
   * Matched text of the tokenizer.
   */
  yytext: '',

  /**
   * Length of the matched text.
   */
  yyleng: 0,

  /**
   * Extra global object lex rules, and other handlers
   * may use to track any needed state across the handlers.
   */
  yy: {},

  /**
   * Stores different parse callbacks.
   */
  yyparse: {
    onParseBegin: function onParseBegin() {},
    onParseEnd: function onParseEnd() {}
  },

  /**
   * Creates location object.
   */
  yyloc: function yyloc(start, end) {
    // Epsilon doesn't produce location.
    if (!start || !end) {
      return start || end;
    }

    return {
      startOffset: start.startOffset,
      endOffset: end.endOffset,
      startLine: start.startLine,
      endLine: end.endLine,
      startColumn: start.startColumn,
      endColumn: end.endColumn
    };
  },


  /**
   * Result value of production handlers, used as $$.
   */
  __: null,

  /**
   * Result node location object.
   */
  __loc: null,

  /**
   * To require modules.
   */
  require: require
});

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

var context = new _vm2.default.createContext(SANDBOX);

/**
 * Evaluation unit for different handlers (of lex rules, productions, etc),
 * which shares the same global space via sandbox.
 */
var CodeUnit = {
  /**
   * Sets value to bindings.
   */
  setBindings: function setBindings(bindings) {
    Object.assign(SANDBOX, bindings);
  },


  /**
   * Evaluates the code. If `shouldRewrite` is `true` (default),
   * rewrites $1, $2, $$, etc. to _1, _2, __, etc.
   */
  eval: function _eval(code) {
    var shouldRewrite = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

    if (shouldRewrite) {
      code = this._rewriteParamsInCode(code);
    }
    return _vm2.default.runInContext(code, context);
  },


  /**
   * Creates parameters string for a semantic action.
   *
   * Consists of: positioned arguments, named arguments,
   * and location data.
   *
   * Example of using the arguments in a handler:
   *
   * $1, $2, $expr, $term, @1, @2
   *
   * Created parameters: _1, _2, _expr, _term, _1loc, _2loc
   */
  createProductionParamsArray: function createProductionParamsArray(_ref) {
    var production = _ref.production,
        captureLocations = _ref.captureLocations;

    if (production.isEpsilon()) {
      return [];
    }

    var symbols = production.getRHS().map(function (symbol) {
      return symbol.getSymbol();
    });

    // $1, $2, ...
    var semanticValues = [];

    // @1, @2, ...
    var locations = captureLocations ? [] : null;

    for (var i = 0; i < symbols.length; i++) {
      var semanticValue = '_' + (i + 1);

      semanticValues.push(semanticValue);

      if (captureLocations) {
        locations.push(semanticValue + 'loc');
      }
    }

    var params = captureLocations ? semanticValues.concat(locations) : semanticValues;

    return params;
  },


  /**
   * See `createProductionParamsArray`.
   */
  createProductionParams: function createProductionParams(_ref2) {
    var production = _ref2.production,
        captureLocations = _ref2.captureLocations;

    return this.createProductionParamsArray({
      production: production,
      captureLocations: captureLocations
    }).join(', ');
  },


  /**
   * Creates default location prologue to semantic action. Begin is
   * taken from first symbol on RHS, the end -- from the last one.
   *
   * @$.startOffset = @1.startOffset
   * @$.endOffset = @1.endOffset
   * ...
   */
  createLocationPrologue: function createLocationPrologue(production) {
    if (production.isEpsilon()) {
      return '__loc = null;';
    }

    var start = 1;
    var end = production.getRHS().length;

    return '__loc = yyloc(_' + start + 'loc, _' + end + 'loc);';
  },


  /**
   * Creates a handler for a production. Attaches default
   * location prologue (user code can override it).
   */
  createProductionHandler: function createProductionHandler(_ref3) {
    var production = _ref3.production,
        captureLocations = _ref3.captureLocations;

    var params = this.createProductionParams({ production: production, captureLocations: captureLocations });

    var locationPrologue = captureLocations ? this.createLocationPrologue(production) : '';

    var action = production.getRawSemanticAction();

    return this.createHandler(params, locationPrologue + action);
  },


  /**
   * Rewrites $1, @1, $name to _1, _1loc, _name
   */
  _rewriteParamsInCode: function _rewriteParamsInCode(code) {
    return code.replace(/\$(\d+)/g, '_$1').replace(/@(\d+)/g, '_$1loc').replace(/\$\$/g, '__').replace(/@\$/g, '__loc');
  },


  /**
   * Creates a handler function with code, and parameters.
   */
  createHandler: function createHandler(parameters, code) {
    return this.eval('(function(' + parameters + ') { ' + code + ' })');
  },


  /**
   * Returns evaluation sandbox.
   */
  getSandbox: function getSandbox() {
    return SANDBOX;
  }
};

exports.default = CodeUnit;