"use strict";

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

exports.default = function (ast, strictCode, env, realm) {
  let expr = env.evaluate(ast.argument, strictCode);

  if (ast.operator === "delete") {
    return evaluateDeleteOperation(expr, realm);
  }
  if (realm.isInPureScope()) {
    return tryToEvaluateOperationOrLeaveAsAbstract(ast, expr, evaluateOperation, strictCode, realm);
  } else {
    return evaluateOperation(ast, expr, strictCode, realm);
  }
};

var _completions = require("../completions.js");

var _errors = require("../errors.js");

var _index = require("../domains/index.js");

var _index2 = require("../values/index.js");

var _environment = require("../environment.js");

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

var _index3 = require("../methods/index.js");

var _singletons = require("../singletons.js");

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

/**
 * Copyright (c) 2017-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

function isInstance(proto, Constructor) {
  return proto instanceof Constructor || proto === Constructor.prototype;
}

function evaluateDeleteOperation(expr, realm) {
  // ECMA262 12.5.3.2

  // 1. Let ref be the result of evaluating UnaryExpression.
  let ref = expr;

  // 2. ReturnIfAbrupt(ref).

  // 3. If Type(ref) is not Reference, return true.
  if (!(ref instanceof _environment.Reference)) return realm.intrinsics.true;

  // 4. If IsUnresolvableReference(ref) is true, then
  if (_singletons.Environment.IsUnresolvableReference(realm, ref)) {
    // a. Assert: IsStrictReference(ref) is false.
    (0, _invariant2.default)(!_singletons.Environment.IsStrictReference(realm, ref), "did not expect a strict reference");

    // b. Return true.
    return realm.intrinsics.true;
  }

  // 5. If IsPropertyReference(ref) is true, then
  if (_singletons.Environment.IsPropertyReference(realm, ref)) {
    // a. If IsSuperReference(ref) is true, throw a ReferenceError exception.
    if (_singletons.Environment.IsSuperReference(realm, ref)) {
      throw realm.createErrorThrowCompletion(realm.intrinsics.ReferenceError);
    }

    // b. Let baseObj be ! ToObject(GetBase(ref)).
    let base = _singletons.Environment.GetBase(realm, ref);
    // Constructing the reference checks that base is coercible to an object hence
    (0, _invariant2.default)(base instanceof _index2.ConcreteValue || base instanceof _index2.AbstractObjectValue);
    let baseObj = base instanceof _index2.ConcreteValue ? _singletons.To.ToObject(realm, base) : base;

    // c. Let deleteStatus be ? baseObj.[[Delete]](GetReferencedName(ref)).
    let deleteStatus = baseObj.$Delete(_singletons.Environment.GetReferencedName(realm, ref));

    // d. If deleteStatus is false and IsStrictReference(ref) is true, throw a TypeError exception.
    if (!deleteStatus && _singletons.Environment.IsStrictReference(realm, ref)) {
      throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError);
    }

    // e. Return deleteStatus.
    return new _index2.BooleanValue(realm, deleteStatus);
  }

  // 6. Else ref is a Reference to an Environment Record binding,
  // a. Let bindings be GetBase(ref).
  let bindings = _singletons.Environment.GetBase(realm, ref);
  (0, _invariant2.default)(bindings instanceof _environment.EnvironmentRecord);

  // b. Return ? bindings.DeleteBinding(GetReferencedName(ref)).
  let referencedName = _singletons.Environment.GetReferencedName(realm, ref);
  (0, _invariant2.default)(typeof referencedName === "string");
  return new _index2.BooleanValue(realm, bindings.DeleteBinding(referencedName));
}

function tryToEvaluateOperationOrLeaveAsAbstract(ast, expr, func, strictCode, realm) {
  let effects;
  try {
    effects = realm.evaluateForEffects(() => func(ast, expr, strictCode, realm), undefined, "tryToEvaluateOperationOrLeaveAsAbstract");
  } catch (error) {
    if (error instanceof _errors.FatalError) {
      return realm.evaluateWithPossibleThrowCompletion(() => {
        let value = _singletons.Environment.GetValue(realm, expr);

        // if the value is abstract, then create a unary op for it,
        // otherwise we rethrow the error as we don't handle it at this
        // point in time
        if (value instanceof _index2.AbstractValue) {
          return _index2.AbstractValue.createFromUnaryOp(realm, ast.operator, value);
        }
        throw error;
      }, _index.TypesDomain.topVal, _index.ValuesDomain.topVal);
    } else {
      throw error;
    }
  }
  let completion = effects[0];
  if (completion instanceof _completions.PossiblyNormalCompletion) {
    // in this case one of the branches may complete abruptly, which means that
    // not all control flow branches join into one flow at this point.
    // Consequently we have to continue tracking changes until the point where
    // all the branches come together into one.
    completion = realm.composeWithSavedCompletion(completion);
  }

  // Note that the effects of (non joining) abrupt branches are not included
  // in joinedEffects, but are tracked separately inside completion.
  realm.applyEffects(effects);
  // return or throw completion
  if (completion instanceof _completions.AbruptCompletion) throw completion;
  (0, _invariant2.default)(completion instanceof _index2.Value);
  return completion;
}

function evaluateOperation(ast, expr, strictCode, realm) {
  function reportError() {
    let error = new _errors.CompilerDiagnostic("might be a symbol or an object with an unknown valueOf or toString or Symbol.toPrimitive method", ast.argument.loc, "PP0008", "RecoverableError");
    if (realm.handleError(error) === "Fail") throw new _errors.FatalError();
  }

  if (ast.operator === "+") {
    // ECMA262 12.5.6.1

    // 1. Let expr be the result of evaluating UnaryExpression.
    expr;

    // 2. Return ? ToNumber(? GetValue(expr)).
    let value = _singletons.Environment.GetValue(realm, expr);
    if (value instanceof _index2.AbstractValue) {
      if (!_singletons.To.IsToNumberPure(realm, value)) reportError();
      return _index2.AbstractValue.createFromUnaryOp(realm, "+", value);
    }
    (0, _invariant2.default)(value instanceof _index2.ConcreteValue);

    return _index2.IntegralValue.createFromNumberValue(realm, _singletons.To.ToNumber(realm, value));
  } else if (ast.operator === "-") {
    // ECMA262 12.5.7.1

    // 1. Let expr be the result of evaluating UnaryExpression.
    expr;

    // 2. Let oldValue be ? ToNumber(? GetValue(expr)).
    let value = _singletons.Environment.GetValue(realm, expr);
    if (value instanceof _index2.AbstractValue) {
      if (!_singletons.To.IsToNumberPure(realm, value)) reportError();
      return _index2.AbstractValue.createFromUnaryOp(realm, "-", value);
    }
    (0, _invariant2.default)(value instanceof _index2.ConcreteValue);
    let oldValue = _singletons.To.ToNumber(realm, value);

    // 3. If oldValue is NaN, return NaN.
    if (isNaN(oldValue)) {
      return realm.intrinsics.NaN;
    }

    // 4. Return the result of negating oldValue; that is, compute a Number with the same magnitude but opposite sign.
    return _index2.IntegralValue.createFromNumberValue(realm, -oldValue);
  } else if (ast.operator === "~") {
    // ECMA262 12.5.8

    // 1. Let expr be the result of evaluating UnaryExpression.
    expr;

    // 2. Let oldValue be ? ToInt32(? GetValue(expr)).
    let value = _singletons.Environment.GetValue(realm, expr);
    if (value instanceof _index2.AbstractValue) {
      if (!_singletons.To.IsToNumberPure(realm, value)) reportError();
      return _index2.AbstractValue.createFromUnaryOp(realm, "~", value);
    }
    (0, _invariant2.default)(value instanceof _index2.ConcreteValue);
    let oldValue = _singletons.To.ToInt32(realm, value);

    // 3. Return the result of applying bitwise complement to oldValue. The result is a signed 32-bit integer.
    return _index2.IntegralValue.createFromNumberValue(realm, ~oldValue);
  } else if (ast.operator === "!") {
    // ECMA262 12.6.9

    // 1. Let expr be the result of evaluating UnaryExpression.
    expr;

    // 2. Let oldValue be ToBoolean(? GetValue(expr)).
    let value = _singletons.Environment.GetConditionValue(realm, expr);
    if (value instanceof _index2.AbstractValue) return _index2.AbstractValue.createFromUnaryOp(realm, "!", value);
    (0, _invariant2.default)(value instanceof _index2.ConcreteValue);
    let oldValue = _singletons.To.ToBoolean(realm, value);

    // 3. If oldValue is true, return false.
    if (oldValue === true) return realm.intrinsics.false;

    // 4. Return true.
    return realm.intrinsics.true;
  } else if (ast.operator === "void") {
    // 1. Let expr be the result of evaluating UnaryExpression.
    expr;

    // 2. Perform ? GetValue(expr).
    _singletons.Environment.GetValue(realm, expr);

    // 3. Return undefined.
    return realm.intrinsics.undefined;
  } else {
    (0, _invariant2.default)(ast.operator === "typeof");
    // ECMA262 12.6.5

    // 1. Let val be the result of evaluating UnaryExpression.
    let val = expr;

    // 2. If Type(val) is Reference, then
    if (val instanceof _environment.Reference) {
      // a. If IsUnresolvableReference(val) is true, return "undefined".
      if (_singletons.Environment.IsUnresolvableReference(realm, val)) {
        return new _index2.StringValue(realm, "undefined");
      }
    }

    // 3. Let val be ? GetValue(val).
    val = _singletons.Environment.GetValue(realm, val);

    // 4. Return a String according to Table 35.
    let proto = val.getType().prototype;
    if (isInstance(proto, _index2.UndefinedValue)) {
      return new _index2.StringValue(realm, "undefined");
    } else if (isInstance(proto, _index2.NullValue)) {
      return new _index2.StringValue(realm, "object");
    } else if (isInstance(proto, _index2.StringValue)) {
      return new _index2.StringValue(realm, "string");
    } else if (isInstance(proto, _index2.BooleanValue)) {
      return new _index2.StringValue(realm, "boolean");
    } else if (isInstance(proto, _index2.NumberValue)) {
      return new _index2.StringValue(realm, "number");
    } else if (isInstance(proto, _index2.SymbolValue)) {
      return new _index2.StringValue(realm, "symbol");
    } else if (isInstance(proto, _index2.ObjectValue)) {
      if ((0, _index3.IsCallable)(realm, val)) {
        return new _index2.StringValue(realm, "function");
      }
      return new _index2.StringValue(realm, "object");
    } else {
      (0, _invariant2.default)(val instanceof _index2.AbstractValue);
      return _index2.AbstractValue.createFromUnaryOp(realm, "typeof", val);
    }
  }
}
//# sourceMappingURL=UnaryExpression.js.map