"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ArgumentListEvaluation = ArgumentListEvaluation;
exports.Invoke = Invoke;
exports.EvaluateCall = EvaluateCall;
exports.PrepareForOrdinaryCall = PrepareForOrdinaryCall;
exports.OrdinaryCallBindThis = OrdinaryCallBindThis;
exports.OrdinaryCallEvaluateBody = OrdinaryCallEvaluateBody;
exports.EvaluateDirectCall = EvaluateDirectCall;
exports.EvaluateDirectCallWithArgList = EvaluateDirectCallWithArgList;
exports.PrepareForTailCall = PrepareForTailCall;
exports.Call = Call;

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

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

var _realm = require("../realm.js");

var _Value = require("../values/Value.js");

var _Value2 = _interopRequireDefault(_Value);

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

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

var _generator = require("../methods/generator.js");

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

var _get = require("../methods/get.js");

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

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

var _invariant2 = _interopRequireDefault(_invariant);

var _babelTypes = require("babel-types");

var t = _interopRequireWildcard(_babelTypes);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

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

// ECMA262 12.3.6.1
function ArgumentListEvaluation(realm, strictCode, env, argNodes) {
  if (Array.isArray(argNodes)) {
    let args = [];
    for (let node_ of argNodes) {
      if (node_.type === "SpreadElement") {
        let node = node_;
        // 1. Let list be a new empty List.
        let list = args;

        // 2. Let spreadRef be the result of evaluating AssignmentExpression.
        let spreadRef = env.evaluate(node.argument, strictCode);

        // 3. Let spreadObj be ? GetValue(spreadRef).
        let spreadObj = _singletons.Environment.GetValue(realm, spreadRef);

        // 4. Let iterator be ? GetIterator(spreadObj).
        let iterator = (0, _index2.GetIterator)(realm, spreadObj);

        // 5. Repeat
        while (true) {
          // a. Let next be ? IteratorStep(iterator).
          let next = (0, _index2.IteratorStep)(realm, iterator);

          // b. If next is false, return list.
          if (!next) {
            break;
          }

          // c. Let nextArg be ? IteratorValue(next).
          let nextArg = (0, _index2.IteratorValue)(realm, next);

          // d. Append nextArg as the last element of list.
          list.push(nextArg);
        }
      } else {
        let ref = env.evaluate(node_, strictCode);
        let expr = _singletons.Environment.GetValue(realm, ref);
        args.push(expr);
      }
    }
    return args;
  } else {
    let node = argNodes;
    if (node.expressions.length === 0) {
      // 1. Let templateLiteral be this TemplateLiteral.
      let templateLiteral = node;

      // 2. Let siteObj be GetTemplateObject(templateLiteral).
      let siteObj = (0, _get.GetTemplateObject)(realm, templateLiteral);

      // 3. Return a List containing the one element which is siteObj.
      return [siteObj];
    } else {
      // 1. Let templateLiteral be this TemplateLiteral.
      let templateLiteral = node;

      // 2. Let siteObj be GetTemplateObject(templateLiteral).
      let siteObj = (0, _get.GetTemplateObject)(realm, templateLiteral);

      // 3. Let firstSubRef be the result of evaluating Expression.
      let firstSubRef = env.evaluate(node.expressions[0], strictCode);

      // 4. Let firstSub be ? GetValue(firstSubRef).
      let firstSub = _singletons.Environment.GetValue(realm, firstSubRef);

      // 5. Let restSub be SubstitutionEvaluation of TemplateSpans.
      let restSub = node.expressions.slice(1, node.expressions.length).map(expr => {
        return _singletons.Environment.GetValue(realm, env.evaluate(expr, strictCode));
      });

      // 6. ReturnIfAbrupt(restSub).

      // 7. Assert: restSub is a List.
      (0, _invariant2.default)(restSub.constructor === Array, "restSub is a List");

      // 8. Return a List whose first element is siteObj, whose second elements is firstSub, and whose subsequent elements are the elements of restSub, in order. restSub may contain no elements.
      return [siteObj, firstSub, ...restSub];
    }
  }
}

// ECMA262 7.3.18
/**
 * 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 Invoke(realm, V, P, argumentsList) {
  // 1. Assert: IsPropertyKey(P) is true.
  (0, _invariant2.default)((0, _index2.IsPropertyKey)(realm, P), "expected property key");

  // 2. If argumentsList was not passed, let argumentsList be a new empty List.
  if (!argumentsList) argumentsList = [];

  // 3. Let func be ? GetV(V, P).
  let func = (0, _get.GetV)(realm, V, P);

  // 4. Return ? Call(func, V, argumentsList).
  return Call(realm, func, V, argumentsList);
}

// ECMA262 12.3.4.2
function EvaluateCall(realm, strictCode, env, ref, args) {
  let thisValue;

  // 1. Let func be ? GetValue(ref).
  let func = _singletons.Environment.GetValue(realm, ref);

  // 2. If Type(ref) is Reference, then
  if (ref instanceof _environment.Reference) {
    // a. If IsPropertyReference(ref) is true, then
    if (_singletons.Environment.IsPropertyReference(realm, ref)) {
      // i. Let thisValue be GetThisValue(ref).
      thisValue = (0, _get.GetThisValue)(realm, ref);
    } else {
      // b. Else, the base of ref is an Environment Record
      // i. Let refEnv be GetBase(ref).
      let refEnv = _singletons.Environment.GetBase(realm, ref);
      (0, _invariant2.default)(refEnv instanceof _environment.EnvironmentRecord);

      // ii. Let thisValue be refEnv.WithBaseObject().
      thisValue = refEnv.WithBaseObject();
    }
  } else {
    // 3. Else Type(ref) is not Reference,
    // a. Let thisValue be undefined.
    thisValue = realm.intrinsics.undefined;
  }

  // 4. Return ? EvaluateDirectCall(func, thisValue, arguments, tailPosition).
  return EvaluateDirectCall(realm, strictCode, env, ref, func, thisValue, args);
}

// ECMA262 9.2.1.1
function PrepareForOrdinaryCall(realm, F, newTarget) {
  // 1. Assert: Type(newTarget) is Undefined or Object.
  (0, _invariant2.default)(newTarget === undefined || newTarget instanceof _index.ObjectValue, "expected undefined or object value for new target");

  // 2. Let callerContext be the running execution context.
  let callerContext = realm.getRunningContext();

  // 3. Let calleeContext be a new ECMAScript code execution context.
  let calleeContext = realm.createExecutionContext();

  // 4. Set the Function of calleeContext to F.
  calleeContext.setFunction(F);
  calleeContext.setCaller(realm.getRunningContext());

  // 5. Let calleeRealm be the value of F's [[Realm]] internal slot.
  let calleeRealm = realm;

  // 6. Set the Realm of calleeContext to calleeRealm.
  calleeContext.realm = calleeRealm;

  // 7. Set the ScriptOrModule of calleeContext to the value of F's [[ScriptOrModule]] internal slot.
  calleeContext.ScriptOrModule = F.$ScriptOrModule;

  // 8. Let localEnv be NewFunctionEnvironment(F, newTarget).
  let localEnv = _singletons.Environment.NewFunctionEnvironment(realm, F, newTarget);

  // 9. Set the LexicalEnvironment of calleeContext to localEnv.
  calleeContext.lexicalEnvironment = localEnv;

  // 10. Set the VariableEnvironment of calleeContext to localEnv.
  calleeContext.variableEnvironment = localEnv;

  // 11. If callerContext is not already suspended, suspend callerContext.
  callerContext.suspend();

  // 12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
  realm.pushContext(calleeContext);

  // 13. NOTE Any exception objects produced after this point are associated with calleeRealm.

  // 14. Return calleeContext.
  return calleeContext;
}

// ECMA262 9.2.1.2
function OrdinaryCallBindThis(realm, F, calleeContext, thisArgument) {
  // 1. Let thisMode be the value of F's [[ThisMode]] internal slot.
  let thisMode = F.$ThisMode;

  // 2. If thisMode is lexical, return NormalCompletion(undefined).
  if (thisMode === "lexical") return realm.intrinsics.undefined;

  // 3. Let calleeRealm be the value of F's [[Realm]] internal slot.
  let calleeRealm = F.$Realm;

  // 4. Let localEnv be the LexicalEnvironment of calleeContext.
  let localEnv = calleeContext.lexicalEnvironment;

  let thisValue;
  // 5. If thisMode is strict, let thisValue be thisArgument.
  if (thisMode === "strict") {
    thisValue = thisArgument;
  } else {
    // 6. Else,
    // a. If thisArgument is null or undefined, then
    if ((0, _index2.HasSomeCompatibleType)(thisArgument, _index.NullValue, _index.UndefinedValue)) {
      // i. Let globalEnv be calleeRealm.[[GlobalEnv]].
      let globalEnv = realm.$GlobalEnv;

      // ii. Let globalEnvRec be globalEnv's EnvironmentRecord.
      let globalEnvRec = globalEnv.environmentRecord;
      (0, _invariant2.default)(globalEnvRec instanceof _environment.GlobalEnvironmentRecord);

      // iii. Let thisValue be globalEnvRec.[[GlobalThisValue]].
      thisValue = globalEnvRec.$GlobalThisValue;
    } else {
      //  b. Else,
      // i. Let thisValue be ! ToObject(thisArgument).
      thisValue = _singletons.To.ToObjectPartial(calleeRealm, thisArgument);

      // ii. NOTE ToObject produces wrapper objects using calleeRealm.
    }
  }

  // 7. Let envRec be localEnv's EnvironmentRecord.
  (0, _invariant2.default)(localEnv !== undefined);
  let envRec = localEnv.environmentRecord;

  // 8. Assert: The next step never returns an abrupt completion because envRec.[[ThisBindingStatus]] is not "initialized".

  // 9. Return envRec.BindThisValue(thisValue).
  return envRec.BindThisValue(thisValue);
}

// ECMA262 9.2.1.3
function OrdinaryCallEvaluateBody(realm, f, argumentsList) {
  if (f instanceof _index.NativeFunctionValue) {
    let env = realm.getRunningContext().lexicalEnvironment;
    try {
      return f.callCallback(env.environmentRecord.GetThisBinding(), argumentsList, env.environmentRecord.$NewTarget);
    } catch (err) {
      if (err instanceof _completions.AbruptCompletion) {
        return err;
      } else if (err instanceof Error) {
        throw err;
      } else {
        throw new _errors.FatalError(err);
      }
    }
  } else {
    (0, _invariant2.default)(f instanceof _index.ECMAScriptSourceFunctionValue);
    let F = f;
    if (F.$FunctionKind === "generator") {
      // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList).
      _singletons.Functions.FunctionDeclarationInstantiation(realm, F, argumentsList);

      // 2. Let G be ? OrdinaryCreateFromConstructor(functionObject, "%GeneratorPrototype%", « [[GeneratorState]], [[GeneratorContext]] »).
      let G = _singletons.Create.OrdinaryCreateFromConstructor(realm, F, "GeneratorPrototype", {
        $GeneratorState: undefined,
        $GeneratorContext: undefined
      });

      // 3. Perform GeneratorStart(G, FunctionBody).
      let code = F.$ECMAScriptCode;
      (0, _invariant2.default)(code !== undefined);
      (0, _generator.GeneratorStart)(realm, G, code);

      // 4. Return Completion{[[Type]]: return, [[Value]]: G, [[Target]]: empty}.
      return new _completions.ReturnCompletion(G, realm.currentLocation);
    } else {
      // TODO #1586: abstractRecursionSummarization is disabled for now, as it is likely too limiting
      // (as observed in large internal tests).
      const abstractRecursionSummarization = false;
      if (!realm.useAbstractInterpretation || realm.pathConditions.length === 0 || !abstractRecursionSummarization) return normalCall();
      let savedIsSelfRecursive = F.isSelfRecursive;
      try {
        F.isSelfRecursive = false;
        let effects = realm.evaluateForEffects(guardedCall, undefined, "OrdinaryCallEvaluateBody");
        if (F.isSelfRecursive) {
          _index.AbstractValue.reportIntrospectionError(F, "call to function that calls itself");
          throw new _errors.FatalError();
          //todo: need to emit a specialized function that temporally captures the heap state at this point
        } else {
          realm.applyEffects(effects);
          let c = effects[0];
          return processResult(() => {
            (0, _invariant2.default)(c instanceof _Value2.default || c instanceof _completions.AbruptCompletion);
            return c;
          });
        }
      } finally {
        F.isSelfRecursive = savedIsSelfRecursive;
      }

      function guardedCall() {
        let currentLocation = realm.currentLocation;
        if (F.activeArguments !== undefined && F.activeArguments.has(currentLocation)) {
          let [previousPathLength, previousArguments] = F.activeArguments.get(currentLocation);
          if (realm.pathConditions.length > previousPathLength) {
            (0, _invariant2.default)(previousArguments !== undefined);
            // F is being called recursively while a call to it is still active
            F.isSelfRecursive = true;
            let widenedArgumentsList = _singletons.Widen.widenValues(realm, previousArguments, argumentsList);
            if (_singletons.Widen.containsArraysOfValue(realm, previousArguments, widenedArgumentsList)) {
              // Reached a fixed point. Executing this call will not add any knowledge
              // about the effects of the original call.
              return _index.AbstractValue.createFromType(realm, _Value2.default, "widened return result");
            } else {
              argumentsList = widenedArgumentsList;
            }
          }
        }
        try {
          if (F.activeArguments === undefined) F.activeArguments = new Map();
          F.activeArguments.set(currentLocation, [realm.pathConditions.length, argumentsList]);
          return normalCall();
        } finally {
          F.activeArguments.delete(currentLocation);
        }
      }

      function normalCall() {
        // 1. Perform ? FunctionDeclarationInstantiation(F, argumentsList).
        _singletons.Functions.FunctionDeclarationInstantiation(realm, F, argumentsList);

        // 2. Return the result of EvaluateBody of the parsed code that is the value of F's
        //    [[ECMAScriptCode]] internal slot passing F as the argument.
        let code = F.$ECMAScriptCode;
        (0, _invariant2.default)(code !== undefined);
        let context = realm.getRunningContext();
        return processResult(() => context.lexicalEnvironment.evaluateCompletionDeref(code, F.$Strict));
      }

      function processResult(getCompletion) {
        let priorSavedCompletion = realm.savedCompletion;
        try {
          realm.savedCompletion = undefined;
          let c = getCompletion();
          // We are about the leave this function and this presents a join point where all non exeptional control flows
          // converge into a single flow using the joined effects as the new state.
          c = _singletons.Functions.incorporateSavedCompletion(realm, c);
          let joinedEffects;
          if (c instanceof _completions.PossiblyNormalCompletion) {
            let e = realm.getCapturedEffects(c);
            if (e !== undefined) {
              // There were earlier, conditional exits from the function
              // We join together the current effects with the effects of any earlier returns that are tracked in c.
              realm.stopEffectCaptureAndUndoEffects(c);
            } else {
              e = (0, _realm.construct_empty_effects)(realm);
            }
            joinedEffects = _singletons.Join.joinEffectsAndPromoteNestedReturnCompletions(realm, c, e);
          } else if (c instanceof _completions.JoinedAbruptCompletions) {
            joinedEffects = _singletons.Join.joinEffectsAndPromoteNestedReturnCompletions(realm, c, (0, _realm.construct_empty_effects)(realm));
          }
          if (joinedEffects !== undefined) {
            let result = joinedEffects[0];
            if (result instanceof _completions.ReturnCompletion) {
              realm.applyEffects(joinedEffects);
              return result;
            }
            (0, _invariant2.default)(result instanceof _completions.JoinedAbruptCompletions);
            if (!(result.consequent instanceof _completions.ReturnCompletion || result.alternate instanceof _completions.ReturnCompletion)) {
              realm.applyEffects(joinedEffects);
              throw result;
            }
            // There is a normal return exit, but also one or more throw completions.
            // The throw completions must be extracted into a saved possibly normal completion
            // so that the caller can pick them up in its next completion.
            joinedEffects = extractAndSavePossiblyNormalCompletion(result);
            result = joinedEffects[0];
            (0, _invariant2.default)(result instanceof _completions.ReturnCompletion);
            realm.applyEffects(joinedEffects);
            return result;
          } else {
            (0, _invariant2.default)(c instanceof _Value2.default || c instanceof _completions.AbruptCompletion);
            return c;
          }
        } finally {
          realm.incorporatePriorSavedCompletion(priorSavedCompletion);
        }
      }
    }
  }

  function extractAndSavePossiblyNormalCompletion(c) {
    // There are throw completions that conditionally escape from the the call.
    // We need to carry on in normal mode (after arranging to capturing effects)
    // while stashing away the throw completions so that the next completion we return
    let [joinedEffects, possiblyNormalCompletion] = _singletons.Join.unbundleReturnCompletion(realm, c);
    realm.composeWithSavedCompletion(possiblyNormalCompletion);
    return joinedEffects;
  }
}

// ECMA262 12.3.4.3
function EvaluateDirectCall(realm, strictCode, env, ref, func, thisValue, args, tailPosition) {
  // 1. Let argList be ? ArgumentListEvaluation(arguments).
  let argList = ArgumentListEvaluation(realm, strictCode, env, args);

  return EvaluateDirectCallWithArgList(realm, strictCode, env, ref, func, thisValue, argList, tailPosition);
}

function EvaluateDirectCallWithArgList(realm, strictCode, env, ref, func, thisValue, argList, tailPosition) {
  if (func instanceof _index.AbstractObjectValue && _Value2.default.isTypeCompatibleWith(func.getType(), _index.FunctionValue)) {
    return _index.AbstractValue.createTemporalFromBuildFunction(realm, func.functionResultType || _Value2.default, [func].concat(argList), nodes => {
      let fun_args = nodes.slice(1);
      return t.callExpression(nodes[0], fun_args);
    });
  }
  func = func.throwIfNotConcrete();

  // 2. If Type(func) is not Object, throw a TypeError exception.
  if (!(func instanceof _index.ObjectValue)) {
    throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not an object");
  }

  // 3. If IsCallable(func) is false, throw a TypeError exception.
  if (!(0, _index2.IsCallable)(realm, func)) {
    throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not callable");
  }

  // 4. If tailPosition is true, perform PrepareForTailCall().
  if (tailPosition === true) PrepareForTailCall(realm);

  // 5. Let result be Call(func, thisValue, argList).
  let result = Call(realm, func, thisValue, argList);

  // 6. Assert: If tailPosition is true, the above call will not return here, but instead
  //    evaluation will continue as if the following return has already occurred.

  // 7. Assert: If result is not an abrupt completion, then Type(result) is an ECMAScript language type.
  (0, _invariant2.default)(result instanceof _Value2.default, "expected language value type");

  // 8. Return result.
  return result;
}

// ECMA262 14.6.3
function PrepareForTailCall(realm) {
  // 1. Let leafContext be the running execution context.
  let leafContext = realm.getRunningContext();

  // 2. Suspend leafContext.
  leafContext.suspend();

  // 3. Pop leafContext from the execution context stack. The execution context now on the
  //    top of the stack becomes the running execution context.
  realm.onDestroyScope(leafContext.lexicalEnvironment);
  realm.popContext(leafContext);

  // TODO #1008 4. Assert: leafContext has no further use. It will never be activated as the running execution context.
}

// ECMA262 7.3.12
function Call(realm, F, V, argsList) {
  // 1. If argumentsList was not passed, let argumentsList be a new empty List.
  argsList = argsList || [];

  // 2. If IsCallable(F) is false, throw a TypeError exception.
  if ((0, _index2.IsCallable)(realm, F) === false) {
    throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not callable");
  }
  if (F instanceof _index.AbstractValue && _Value2.default.isTypeCompatibleWith(F.getType(), _index.FunctionValue)) {
    _singletons.Havoc.value(realm, V);
    for (let arg of argsList) {
      _singletons.Havoc.value(realm, arg);
    }
    if (V === realm.intrinsics.undefined) {
      let fullArgs = [F].concat(argsList);
      return _index.AbstractValue.createTemporalFromBuildFunction(realm, _Value2.default, fullArgs, nodes => {
        let fun_args = nodes.slice(1);
        return t.callExpression(nodes[0], fun_args);
      });
    } else {
      let fullArgs = [F, V].concat(argsList);
      return _index.AbstractValue.createTemporalFromBuildFunction(realm, _Value2.default, fullArgs, nodes => {
        let fun_args = nodes.slice(1);
        return t.callExpression(t.memberExpression(nodes[0], t.identifier("call")), fun_args);
      });
    }
  }
  (0, _invariant2.default)(F instanceof _index.ObjectValue);

  // 3. Return ? F.[[Call]](V, argumentsList).
  (0, _invariant2.default)(F.$Call, "no call method on this value");
  return F.$Call(V, argsList);
}
//# sourceMappingURL=call.js.map