"use strict";

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

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

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

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

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

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

var _call = require("./call.js");

var _abstract = require("../methods/abstract.js");

var _construct = require("../methods/construct.js");

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

var _iterator = require("../methods/iterator.js");

var _ObjectExpression = require("../evaluators/ObjectExpression.js");

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

var _traverseFast = require("../utils/traverse-fast.js");

var _traverseFast2 = _interopRequireDefault(_traverseFast);

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

var _invariant2 = _interopRequireDefault(_invariant);

var _parse = require("../utils/parse.js");

var _parse2 = _interopRequireDefault(_parse);

var _strict = require("../utils/strict.js");

var _strict2 = _interopRequireDefault(_strict);

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

function InternalCall(realm, F, thisArgument, argsList, tracerIndex) {
  // 1. Assert: F is an ECMAScript function object.
  (0, _invariant2.default)(F instanceof _index.FunctionValue, "expected function value");

  // Tracing: Give all registered tracers a chance to detour, wrapping around each other if needed.
  while (tracerIndex < realm.tracers.length) {
    let tracer = realm.tracers[tracerIndex];
    let nextIndex = ++tracerIndex;
    let detourResult = tracer.detourCall(F, thisArgument, argsList, undefined, () => InternalCall(realm, F, thisArgument, argsList, nextIndex));
    if (detourResult instanceof _index.Value) return detourResult;
  }

  // 2. If F's [[FunctionKind]] internal slot is "classConstructor", throw a TypeError exception.
  if (F.$FunctionKind === "classConstructor") throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "not callable");

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

  // 4. Let calleeContext be PrepareForOrdinaryCall(F, undefined).
  let calleeContext = (0, _call.PrepareForOrdinaryCall)(realm, F, undefined);
  let calleeEnv = calleeContext.lexicalEnvironment;

  let result;
  try {
    for (let t1 of realm.tracers) t1.beforeCall(F, thisArgument, argsList, undefined);

    // 5. Assert: calleeContext is now the running execution context.
    (0, _invariant2.default)(realm.getRunningContext() === calleeContext, "calleeContext should be current execution context");

    // 6. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
    (0, _call.OrdinaryCallBindThis)(realm, F, calleeContext, thisArgument);

    // 7. Let result be OrdinaryCallEvaluateBody(F, argumentsList).
    result = (0, _call.OrdinaryCallEvaluateBody)(realm, F, argsList);
  } finally {
    // 8. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
    realm.popContext(calleeContext);
    realm.onDestroyScope(calleeContext.lexicalEnvironment);
    if (calleeContext.lexicalEnvironment !== calleeEnv) realm.onDestroyScope(calleeEnv);
    (0, _invariant2.default)(realm.getRunningContext() === callerContext);

    for (let t2 of realm.tracers) t2.afterCall(F, thisArgument, argsList, undefined, result);
  }

  // 9. If result.[[Type]] is return, return NormalCompletion(result.[[Value]]).
  if (result instanceof _completions.ReturnCompletion) {
    return result.value;
  }

  // 10. ReturnIfAbrupt(result).  or if possibly abrupt
  if (result instanceof _completions.Completion) {
    throw result;
  }

  // 11. Return NormalCompletion(undefined).
  return realm.intrinsics.undefined;
}

// ECMA262 9.4.1.1
/**
 * 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 $BoundCall(realm, F, thisArgument, argumentsList) {
  // 1. Let target be the value of F's [[BoundTargetFunction]] internal slot.
  let target = F.$BoundTargetFunction;

  // 2. Let boundThis be the value of F's [[BoundThis]] internal slot.
  let boundThis = F.$BoundThis;

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

  // 4. Let args be a new list containing the same values as the list boundArgs in the same order followed
  //    by the same values as the list argumentsList in the same order.
  let args = boundArgs.concat(argumentsList);

  // 5. Return ? Call(target, boundThis, args).
  return (0, _call.Call)(realm, target, boundThis, args);
}

// ECMA262 9.4.1.2
function $BoundConstruct(realm, F, argumentsList, newTarget) {
  // 1. Let target be the value of F's [[BoundTargetFunction]] internal slot.
  let target = F.$BoundTargetFunction;

  // 2. Assert: target has a [[Construct]] internal method.
  (0, _invariant2.default)(target.$Construct !== undefined, "doesn't have a construct internal method");

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

  // 4. Let args be a new list containing the same values as the list boundArgs in the same order followed
  //    by the same values as the list argumentsList in the same order.
  let args = boundArgs.concat(argumentsList);

  // 5. If SameValue(F, newTarget) is true, let newTarget be target.
  if ((0, _abstract.SameValue)(realm, F, newTarget)) newTarget = target;

  // 6. Return ? Construct(target, args, newTarget).
  return (0, _construct.Construct)(realm, target, args, newTarget);
}

function InternalConstruct(realm, F, argumentsList, newTarget, thisArgument, tracerIndex) {
  // 1. Assert: F is an ECMAScript function object.
  (0, _invariant2.default)(F instanceof _index.FunctionValue, "expected function");

  // 2. Assert: Type(newTarget) is Object.
  (0, _invariant2.default)(newTarget instanceof _index.ObjectValue, "expected object");

  if (!realm.hasRunningContext()) {
    (0, _invariant2.default)(realm.useAbstractInterpretation);
    throw new _errors.FatalError("no running context");
  }

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

  // 4. Let kind be F's [[ConstructorKind]] internal slot.
  let kind = F.$ConstructorKind;

  // 5. If kind is "base", then
  if (thisArgument === undefined && kind === "base") {
    // a. Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%ObjectPrototype%").
    thisArgument = _singletons.Create.OrdinaryCreateFromConstructor(realm, newTarget, "ObjectPrototype");
  }

  // Tracing: Give all registered tracers a chance to detour, wrapping around each other if needed.
  while (tracerIndex < realm.tracers.length) {
    let tracer = realm.tracers[tracerIndex];
    let nextIndex = ++tracerIndex;
    let detourResult = tracer.detourCall(F, thisArgument, argumentsList, newTarget, () => InternalConstruct(realm, F, argumentsList, newTarget, thisArgument, nextIndex));
    if (detourResult instanceof _index.ObjectValue) return detourResult;
    (0, _invariant2.default)(detourResult === undefined);
  }

  // 6. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
  let calleeContext = (0, _call.PrepareForOrdinaryCall)(realm, F, newTarget);
  let calleeEnv = calleeContext.lexicalEnvironment;

  // 7. Assert: calleeContext is now the running execution context.
  (0, _invariant2.default)(realm.getRunningContext() === calleeContext, "expected calleeContext to be running context");

  let result, envRec;
  try {
    for (let t1 of realm.tracers) t1.beforeCall(F, thisArgument, argumentsList, newTarget);

    // 8. If kind is "base", perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
    if (kind === "base") {
      (0, _invariant2.default)(thisArgument, "this wasn't initialized for some reason");
      (0, _call.OrdinaryCallBindThis)(realm, F, calleeContext, thisArgument);
    }

    // 9. Let constructorEnv be the LexicalEnvironment of calleeContext.
    let constructorEnv = calleeContext.lexicalEnvironment;

    // 10. Let envRec be constructorEnv's EnvironmentRecord.
    envRec = constructorEnv.environmentRecord;

    // 11. Let result be OrdinaryCallEvaluateBody(F, argumentsList).
    result = (0, _call.OrdinaryCallEvaluateBody)(realm, F, argumentsList);
  } finally {
    // 12. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
    realm.popContext(calleeContext);
    realm.onDestroyScope(calleeContext.lexicalEnvironment);
    if (calleeContext.lexicalEnvironment !== calleeEnv) realm.onDestroyScope(calleeEnv);
    (0, _invariant2.default)(realm.getRunningContext() === callerContext);

    for (let t2 of realm.tracers) t2.afterCall(F, thisArgument, argumentsList, newTarget, result);
  }

  // 13. If result.[[Type]] is return, then
  if (result instanceof _completions.ReturnCompletion) {
    // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
    if (result.value.mightBeObject()) {
      return result.value.throwIfNotConcreteObject();
    }

    // b. If kind is "base", return NormalCompletion(thisArgument).
    if (kind === "base") {
      (0, _invariant2.default)(thisArgument, "this wasn't initialized for some reason");
      return thisArgument;
    }

    // c. If result.[[Value]] is not undefined, throw a TypeError exception.
    if (!result.value.mightBeUndefined()) throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "constructor must return Object");
    result.value.throwIfNotConcrete();
  } else if (result instanceof _completions.AbruptCompletion) {
    // 14. Else, ReturnIfAbrupt(result).
    throw result;
  }

  // 15. Return ? envRec.GetThisBinding().
  let envRecThisBinding = envRec.GetThisBinding();
  (0, _invariant2.default)(envRecThisBinding instanceof _index.ObjectValue);
  return envRecThisBinding;
}

class FunctionImplementation {
  FindVarScopedDeclarations(ast_node) {
    function FindVarScopedDeclarationsFor(ast, level) {
      let statements = [];
      switch (ast.type) {
        case "Program":
          statements = ast.body;
          break;
        case "BlockStatement":
          statements = ast.body;
          break;
        case "DoWhileStatement":
          statements = [ast.body];
          break;
        case "WhileStatement":
          statements = [ast.body];
          break;
        case "IfStatement":
          let astIfStatement = ast;
          statements = [astIfStatement.consequent, astIfStatement.alternate];
          break;
        case "ForStatement":
          let astForStatement = ast;
          statements = [astForStatement.init, astForStatement.body];
          break;
        case "ForInStatement":
          let astForInStatement = ast;
          statements = [astForInStatement.left, astForInStatement.body];
          break;
        case "ForOfStatement":
          let astForOfStatement = ast;
          statements = [astForOfStatement.left, astForOfStatement.body];
          break;
        case "LabeledStatement":
          statements = [ast.body];
          break;
        case "WithStatement":
          statements = [ast.body];
          break;
        case "SwitchStatement":
          for (let switchCase of ast.cases) {
            statements = statements.concat(switchCase.consequent);
          }
          break;
        case "TryStatement":
          let astTryStatement = ast;
          statements = [astTryStatement.block];
          if (astTryStatement.finalizer) statements.push(astTryStatement.finalizer);
          if (astTryStatement.handler) statements.push(astTryStatement.handler.body);
          break;
        case "VariableDeclaration":
          return ast.kind === "var" ? [ast] : [];
        case "FunctionDeclaration":
          return level < 2 ? [ast] : [];
        default:
          return [];
      }

      let decls = [];
      for (let statement of statements) {
        if (statement) {
          decls = decls.concat(FindVarScopedDeclarationsFor(statement, level + 1));
        }
      }

      return decls;
    }
    return FindVarScopedDeclarationsFor(ast_node, 0);
  }

  // ECMA262 9.2.12
  FunctionDeclarationInstantiation(realm, func, argumentsList) {
    // 1. Let calleeContext be the running execution context.
    let calleeContext = realm.getRunningContext();

    // 2. Let env be the LexicalEnvironment of calleeContext.
    let env = calleeContext.lexicalEnvironment;

    // 3. Let envRec be env's EnvironmentRecord.
    let envRec = env.environmentRecord;

    // 4. Let code be the value of the [[ECMAScriptCode]] internal slot of func.
    let code = func.$ECMAScriptCode;
    (0, _invariant2.default)(code !== undefined);

    // 5. Let strict be the value of the [[Strict]] internal slot of func.
    let strict = func.$Strict;

    // 6. Let formals be the value of the [[FormalParameters]] internal slot of func.
    let formals = func.$FormalParameters;
    (0, _invariant2.default)(formals !== undefined);

    // 7. Let parameterNames be the BoundNames of formals.
    let parameterNames = Object.create(null);
    for (let param of formals) {
      let paramBindings = t.getBindingIdentifiers(param, true);

      for (let name in paramBindings) {
        parameterNames[name] = (parameterNames[name] || []).concat(paramBindings[name]);
      }
    }

    // 8. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false.
    let hasDuplicates = false;
    for (let name in parameterNames) {
      let identifiers = parameterNames[name];
      if (identifiers.length > 1) hasDuplicates = true;
    }
    parameterNames = Object.keys(parameterNames);

    // 9. Let simpleParameterList be IsSimpleParameterList of formals.
    let simpleParameterList = true;
    for (let param of formals) {
      if (param.type !== "Identifier") {
        simpleParameterList = false;
        break;
      }
    }

    // 10. Let hasParameterExpressions be ContainsExpression of formals.
    let hasParameterExpressions = false;
    (0, _invariant2.default)(formals !== undefined);
    for (let param of formals) {
      if (_singletons.Environment.ContainsExpression(realm, param)) {
        hasParameterExpressions = true;
        break;
      }
    }

    // 11. Let varNames be the VarDeclaredNames of code.
    let varNames = [];
    (0, _traverseFast2.default)(code, node => {
      if (node.type === "VariableDeclaration" && node.kind === "var") {
        varNames = varNames.concat(Object.keys(t.getBindingIdentifiers(node)));
      }

      if (node.type === "FunctionExpression" || node.type === "FunctionDeclaration") {
        return true;
      }

      return false;
    });

    // 12. Let varDeclarations be the VarScopedDeclarations of code.
    let varDeclarations = this.FindVarScopedDeclarations(code);

    // 13. Let lexicalNames be the LexicallyDeclaredNames of code.
    let lexicalNames = [];

    // 14. Let functionNames be an empty List.
    let functionNames = [];

    // 15. Let functionsToInitialize be an empty List.
    let functionsToInitialize = [];

    // 16. For each d in varDeclarations, in reverse list order do
    for (let d of varDeclarations.reverse()) {
      // a. If d is neither a VariableDeclaration or a ForBinding, then
      if (d.type !== "VariableDeclaration") {
        // i. Assert: d is either a FunctionDeclaration or a GeneratorDeclaration.
        (0, _invariant2.default)(d.type === "FunctionDeclaration" || d.type === "GeneratorDeclaration");
        // ii. Let fn be the sole element of the BoundNames of d.
        let fn = _singletons.Environment.BoundNames(realm, d)[0];
        // iii. If fn is not an element of functionNames, then
        if (functionNames.indexOf(fn) < 0) {
          // 1. Insert fn as the first element of functionNames.
          functionNames.unshift(fn);
          // 2. NOTE If there are multiple FunctionDeclarations or GeneratorDeclarations for the same name, the last declaration is used.
          // 3. Insert d as the first element of functionsToInitialize.
          functionsToInitialize.unshift(d);
        }
      }
    }

    // 17. Let argumentsObjectNeeded be true.
    let argumentsObjectNeeded = true;

    // 18. If the value of the [[realmMode]] internal slot of func is lexical, then
    if (func.$ThisMode === "lexical") {
      // a. NOTE Arrow functions never have an arguments objects.
      // b. Let argumentsObjectNeeded be false.
      argumentsObjectNeeded = false;
    } else if (parameterNames.indexOf("arguments") >= 0) {
      // 19. Else if "arguments" is an element of parameterNames, then
      // a. Let argumentsObjectNeeded be false.
      argumentsObjectNeeded = false;
    } else if (hasParameterExpressions === false) {
      // 20. Else if hasParameterExpressions is false, then
      // a. If "arguments" is an element of functionNames or if "arguments" is an element of lexicalNames, then
      if (functionNames.indexOf("arguments") >= 0 || lexicalNames.indexOf("arguments") >= 0) {
        // i. Let argumentsObjectNeeded be false.
        argumentsObjectNeeded = true;
      }
    }

    // 21. For each String paramName in parameterNames, do
    for (let paramName of parameterNames) {
      // a. Let alreadyDeclared be envRec.HasBinding(paramName).
      let alreadyDeclared = envRec.HasBinding(paramName);

      // b. NOTE Early errors ensure that duplicate parameter names can only occur in non-strict functions that do not have parameter default values or rest parameters.

      // c. If alreadyDeclared is false, then
      if (alreadyDeclared === false) {
        // i. Perform ! envRec.CreateMutableBinding(paramName, false).
        envRec.CreateMutableBinding(paramName, false);

        // ii. If hasDuplicates is true, then
        if (hasDuplicates === true) {
          // 1. Perform ! envRec.InitializeBinding(paramName, undefined).
          envRec.InitializeBinding(paramName, realm.intrinsics.undefined);
        }
      }
    }

    // 22. If argumentsObjectNeeded is true, then
    if (argumentsObjectNeeded === true) {
      let ao;

      // a. If strict is true or if simpleParameterList is false, then
      if (strict === true || simpleParameterList === false) {
        // i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
        ao = _singletons.Create.CreateUnmappedArgumentsObject(realm, argumentsList);
      } else {
        // b. Else,
        // i. NOTE mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters.
        // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, envRec).
        (0, _invariant2.default)(formals !== undefined);
        ao = _singletons.Create.CreateMappedArgumentsObject(realm, func, formals, argumentsList, envRec);
      }

      // c. If strict is true, then
      if (strict === true) {
        // i. Perform ! envRec.CreateImmutableBinding("arguments", false).
        envRec.CreateImmutableBinding("arguments", false);
      } else {
        // d. Else,
        // i. Perform ! envRec.CreateMutableBinding("arguments", false).
        envRec.CreateMutableBinding("arguments", false);
      }

      // e. Call envRec.InitializeBinding("arguments", ao).
      envRec.InitializeBinding("arguments", ao);

      // f. Append "arguments" to parameterNames.
      parameterNames.push("arguments");
    }

    // 23. Let iteratorRecord be Record {[[Iterator]]: CreateListIterator(argumentsList), [[Done]]: false}.
    let iteratorRecord = {
      $Iterator: (0, _iterator.CreateListIterator)(realm, argumentsList),
      $Done: false
    };

    // 24. If hasDuplicates is true, then
    if (hasDuplicates === true) {
      // a. Perform ? IteratorBindingInitialization for formals with iteratorRecord and undefined as arguments.
      (0, _invariant2.default)(formals !== undefined);
      _singletons.Environment.IteratorBindingInitialization(realm, formals, iteratorRecord, strict);
    } else {
      // 25. Else,
      // a. Perform ? IteratorBindingInitialization for formals with iteratorRecord and env as arguments.
      (0, _invariant2.default)(formals !== undefined);
      _singletons.Environment.IteratorBindingInitialization(realm, formals, iteratorRecord, strict, env);
    }

    // 26. If hasParameterExpressions is false, then
    let varEnv, varEnvRec;
    if (hasParameterExpressions === false) {
      // a. NOTE Only a single lexical environment is needed for the parameters and top-level vars.
      // b. Let instantiatedVarNames be a copy of the List parameterNames.
      let instantiatedVarNames = parameterNames.slice();

      // c. For each n in varNames, do
      for (let n of varNames) {
        // i. If n is not an element of instantiatedVarNames, then
        if (instantiatedVarNames.indexOf(n) < 0) {
          // 1. Append n to instantiatedVarNames.
          instantiatedVarNames.push(n);

          // 2. Perform ! envRec.CreateMutableBinding(n, false).
          envRec.CreateMutableBinding(n, false);

          // 3. Call envRec.InitializeBinding(n, undefined).
          envRec.InitializeBinding(n, realm.intrinsics.undefined);
        }
      }

      // e. Let varEnv be env.
      varEnv = env;

      // f. Let varEnvRec be envRec.
      varEnvRec = envRec;
    } else {
      // 27. Else,
      // a. NOTE A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.

      // b. Let varEnv be NewDeclarativeEnvironment(env).
      varEnv = _singletons.Environment.NewDeclarativeEnvironment(realm, env);
      // At this point we haven't set any context's lexical environment to varEnv (and we might never do so),
      // so it shouldn't be active
      realm.activeLexicalEnvironments.delete(varEnv);

      // c. Let varEnvRec be varEnv's EnvironmentRecord.
      varEnvRec = varEnv.environmentRecord;

      // d. Set the VariableEnvironment of calleeContext to varEnv.
      calleeContext.variableEnvironment = varEnv;

      // e. Let instantiatedVarNames be a new empty List.
      let instantiatedVarNames = [];

      // f. For each n in varNames, do
      for (let n of varNames) {
        // i. If n is not an element of instantiatedVarNames, then
        if (instantiatedVarNames.indexOf(n) < 0) {
          // 1. Append n to instantiatedVarNames.
          instantiatedVarNames.push(n);

          // 2. Perform ! varEnvRec.CreateMutableBinding(n, false).
          varEnvRec.CreateMutableBinding(n, false);

          // 3. If n is not an element of parameterNames or if n is an element of functionNames, let initialValue be undefined.
          let initialValue;
          if (parameterNames.indexOf(n) < 0 || functionNames.indexOf(n) < 0) {
            initialValue = realm.intrinsics.undefined;
          } else {
            // 4. Else,
            // a. Let initialValue be ! envRec.GetBindingValue(n, false).
            initialValue = envRec.GetBindingValue(n, false);
          }

          // 5. Call varEnvRec.InitializeBinding(n, initialValue).
          varEnvRec.InitializeBinding(n, initialValue);

          // 6. NOTE vars whose names are the same as a formal parameter, initially have the same value as the corresponding initialized parameter.
        }
      }
    }

    // 28. NOTE: Annex B.3.3.1 adds additional steps at realm point.

    let lexEnv;

    // 29. If strict is false, then
    if (strict === false) {
      // a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
      lexEnv = _singletons.Environment.NewDeclarativeEnvironment(realm, varEnv);

      // b. NOTE: Non-strict functions use a separate lexical Environment Record for top-level lexical declarations so that a direct eval (see 12.3.4.1) can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. realm is not needed for strict functions because a strict direct eval always places all declarations into a new Environment Record.
    } else {
      // 30. Else, let lexEnv be varEnv.
      lexEnv = varEnv;
      // Since we previously removed varEnv, make sure to re-add it when it's used.
      realm.activeLexicalEnvironments.add(varEnv);
    }

    // 31. Let lexEnvRec be lexEnv's EnvironmentRecord.
    let lexEnvRec = lexEnv.environmentRecord;

    // 32. Set the LexicalEnvironment of calleeContext to lexEnv.
    calleeContext.lexicalEnvironment = lexEnv;

    // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
    let lexDeclarations = [];

    // 34. For each element d in lexDeclarations do
    for (let d of lexDeclarations) {
      // a. NOTE A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
      // b. For each element dn of the BoundNames of d do
      for (let dn of _singletons.Environment.BoundNames(realm, d)) {
        // i. If IsConstantDeclaration of d is true, then
        if (d.kind === "const") {
          // 1. Perform ! lexEnvRec.CreateImmutableBinding(dn, true).
          lexEnvRec.CreateImmutableBinding(dn, true);
        } else {
          // ii. Else,
          // 1. Perform ! lexEnvRec.CreateMutableBinding(dn, false).
          lexEnvRec.CreateMutableBinding(dn, false);
        }
      }
    }

    // 35. For each parsed grammar phrase f in functionsToInitialize, do
    for (let f of functionsToInitialize) {
      // a. Let fn be the sole element of the BoundNames of f.
      let fn = _singletons.Environment.BoundNames(realm, f)[0];
      // b. Let fo be the result of performing InstantiateFunctionObject for f with argument lexEnv.
      let fo = lexEnv.evaluate(f, strict);
      (0, _invariant2.default)(fo instanceof _index.Value);
      // c. Perform ! varEnvRec.SetMutableBinding(fn, fo, false).
      varEnvRec.SetMutableBinding(fn, fo, false);
    }

    // 36. Return NormalCompletion(empty).
    return realm.intrinsics.empty;
  }

  // ECMA262 9.2.11
  SetFunctionName(realm, F, name, prefix) {
    // 1. Assert: F is an extensible object that does not have a name own property.
    (0, _invariant2.default)(F.getExtensible(), "expected object to be extensible and not have a name property");

    // 2. Assert: Type(name) is either Symbol or String.
    (0, _invariant2.default)(typeof name === "string" || name instanceof _index.StringValue || name instanceof _index.SymbolValue || name instanceof _index.AbstractValue, "expected name to be a string or symbol");
    if (typeof name === "string") name = new _index.StringValue(realm, name);

    // 3. Assert: If prefix was passed, then Type(prefix) is String.
    (0, _invariant2.default)(prefix === undefined || typeof prefix === "string", "expected prefix to be a string if passed");

    // 4. If Type(name) is Symbol, then
    if (name instanceof _index.SymbolValue) {
      // a. Let description be name's [[Description]] value.
      let description = name.$Description;

      // b. If description is undefined, let name be the empty String.
      if (description === undefined) {
        name = realm.intrinsics.emptyString;
      } else {
        // c. Else, let name be the concatenation of "[", description, and "]".
        (0, _invariant2.default)(description instanceof _index.Value);
        name = new _index.StringValue(realm, `[${description.throwIfNotConcreteString().value}]`);
      }
    }

    // 5. If prefix was passed, then
    if (prefix) {
      // a. Let name be the concatenation of prefix, code unit 0x0020 (SPACE), and name.
      if (name instanceof _index.AbstractValue) {
        let prefixVal = new _index.StringValue(realm, prefix + " ");
        name = _index.AbstractValue.createFromBinaryOp(realm, "+", prefixVal, name, name.expressionLocation);
      } else {
        name = new _index.StringValue(realm, `${prefix} ${name.value}`);
      }
    }

    // 6. Return ! DefinePropertyOrThrow(F, "name", PropertyDescriptor{[[Value]]: name, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}).
    return _singletons.Properties.DefinePropertyOrThrow(realm, F, "name", {
      value: name,
      enumerable: false,
      writable: false,
      configurable: true
    });
  }

  // ECMA262 9.2.3
  FunctionInitialize(realm, F, kind, ParameterList, Body, Scope) {
    // Note that F is a new object, and we can thus write to internal slots
    (0, _invariant2.default)(realm.isNewObject(F));

    // 1. Assert: F is an extensible object that does not have a length own property.
    (0, _invariant2.default)(F.getExtensible(), "expected to be extensible and no length property");

    // 2. Let len be the ExpectedArgumentCount of ParameterList.
    let len = 0;
    for (let FormalParameter of ParameterList) {
      if (FormalParameter.type === "AssignmentPattern") {
        break;
      }
      len += 1;
    }

    // 3. Perform ! DefinePropertyOrThrow(F, "length", PropertyDescriptor{[[Value]]: len, [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}).
    _singletons.Properties.DefinePropertyOrThrow(realm, F, "length", {
      value: new _index.NumberValue(realm, len),
      writable: false,
      enumerable: false,
      configurable: true
    });

    // 4. Let Strict be the value of the [[Strict]] internal slot of F.
    let Strict = F.$Strict;
    if (!Strict) {
      _singletons.Properties.DefinePropertyOrThrow(realm, F, "caller", {
        value: new _index.UndefinedValue(realm),
        writable: true,
        enumerable: false,
        configurable: true
      });
    }

    // 5. Set the [[Environment]] internal slot of F to the value of Scope.
    F.$Environment = Scope;

    // 6. Set the [[FormalParameters]] internal slot of F to ParameterList.
    F.$FormalParameters = ParameterList;

    // 7. Set the [[ECMAScriptCode]] internal slot of F to Body.
    Body.uniqueOrderedTag = realm.functionBodyUniqueTagSeed++;
    F.$ECMAScriptCode = Body;

    // 8. Set the [[ScriptOrModule]] internal slot of F to GetActiveScriptOrModule().
    F.$ScriptOrModule = _singletons.Environment.GetActiveScriptOrModule(realm);

    // 9. If kind is Arrow, set the [[realmMode]] internal slot of F to lexical.
    if (kind === "arrow") {
      F.$ThisMode = "lexical";
    } else if (Strict === true) {
      // 10. Else if Strict is true, set the [[realmMode]] internal slot of F to strict.
      F.$ThisMode = "strict";
    } else {
      // 11. Else set the [[realmMode]] internal slot of F to global.
      F.$ThisMode = "global";
    }

    // Return F.
    return F;
  }

  // ECMA262 9.2.6
  GeneratorFunctionCreate(realm, kind, ParameterList, Body, Scope, Strict) {
    // 1. Let functionPrototype be the intrinsic object %Generator%.
    let functionPrototype = realm.intrinsics.Generator;

    // 2. Let F be FunctionAllocate(functionPrototype, Strict, "generator").
    let F = this.FunctionAllocate(realm, functionPrototype, Strict, "generator");

    // 3. Return FunctionInitialize(F, kind, ParameterList, Body, Scope).
    return this.FunctionInitialize(realm, F, kind, ParameterList, Body, Scope);
  }

  // ECMA262 9.2.7
  AddRestrictedFunctionProperties(F, realm) {
    // 1. Assert: realm.[[Intrinsics]].[[%ThrowTypeError%]] exists and has been initialized.
    // 2. Let thrower be realm.[[Intrinsics]].[[%ThrowTypeError%]].
    let thrower = realm.intrinsics.ThrowTypeError;
    (0, _invariant2.default)(thrower);

    let desc = {
      get: thrower,
      set: thrower,
      enumerable: false,
      configurable: true
    };
    // 3. Perform ! DefinePropertyOrThrow(F, "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: true}).
    _singletons.Properties.DefinePropertyOrThrow(realm, F, "caller", desc);
    // 4. Return ! DefinePropertyOrThrow(F, "arguments", PropertyDescriptor {[[Get]]: thrower, [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: true}).
    return _singletons.Properties.DefinePropertyOrThrow(realm, F, "arguments", desc);
  }

  // ECMA262 9.2.1
  $Call(realm, F, thisArgument, argsList) {
    return InternalCall(realm, F, thisArgument, argsList, 0);
  }

  // ECMA262 9.2.2
  $Construct(realm, F, argumentsList, newTarget) {
    return InternalConstruct(realm, F, argumentsList, newTarget, undefined, 0);
  }

  // ECMA262 9.2.3
  FunctionAllocate(realm, functionPrototype, strict, functionKind) {
    // 1. Assert: Type(functionPrototype) is Object.
    (0, _invariant2.default)(functionPrototype instanceof _index.ObjectValue, "expected functionPrototype to be an object");

    // 2. Assert: functionKind is either "normal", "non-constructor" or "generator".
    (0, _invariant2.default)(functionKind === "normal" || functionKind === "non-constructor" || functionKind === "generator", "invalid functionKind");

    // 3. If functionKind is "normal", let needsConstruct be true.
    let needsConstruct;
    if (functionKind === "normal") {
      needsConstruct = true;
    } else {
      // 4. Else, let needsConstruct be false.
      needsConstruct = false;
    }

    // 5. If functionKind is "non-constructor", let functionKind be "normal".
    if (functionKind === "non-constructor") {
      functionKind = "normal";
    }

    // 6. Let F be a newly created ECMAScript function object with the internal slots listed in Table 27. All of those internal slots are initialized to undefined.
    let F = new _index.ECMAScriptSourceFunctionValue(realm);

    // 7. Set F's essential internal methods to the default ordinary object definitions specified in 9.1.

    // 8. Set F's [[Call]] internal method to the definition specified in 9.2.1.
    F.$Call = (thisArgument, argsList) => {
      return this.$Call(realm, F, thisArgument, argsList);
    };

    // 9. If needsConstruct is true, then
    if (needsConstruct === true) {
      // a. Set F's [[Construct]] internal method to the definition specified in 9.2.2.
      F.$Construct = (argumentsList, newTarget) => {
        return this.$Construct(realm, F, argumentsList, newTarget);
      };

      // b. Set the [[ConstructorKind]] internal slot of F to "base".
      F.$ConstructorKind = "base";
    }

    // 10. Set the [[Strict]] internal slot of F to strict.
    F.$Strict = strict;

    // 11. Set the [[FunctionKind]] internal slot of F to functionKind.
    F.$FunctionKind = functionKind;

    // 12. Set the [[Prototype]] internal slot of F to functionPrototype.
    F.$Prototype = functionPrototype;

    // 13. Set the [[Extensible]] internal slot of F to true.
    F.setExtensible(true);

    // 14. Set the [[Realm]] internal slot of F to the current Realm Record.
    F.$Realm = realm;

    // 15. Return F.
    return F;
  }

  // ECMA262 9.4.1.3
  BoundFunctionCreate(realm, targetFunction, boundThis, boundArgs) {
    // 1. Assert: Type(targetFunction) is Object.
    (0, _invariant2.default)(targetFunction instanceof _index.ObjectValue, "expected an object");

    // 2. Let proto be ? targetFunction.[[GetPrototypeOf]]().
    let proto = targetFunction.$GetPrototypeOf();

    // 3. Let obj be a newly created object.
    let obj = new _index.BoundFunctionValue(realm);

    // 4. Set obj's essential internal methods to the default ordinary object definitions specified in 9.1.

    // 5. Set the [[Call]] internal method of obj as described in 9.4.1.1.
    obj.$Call = (thisArgument, argsList) => {
      return $BoundCall(realm, obj, thisArgument, argsList);
    };

    // 6. If targetFunction has a [[Construct]] internal method, then
    if (targetFunction.$Construct) {
      // a. Set the [[Construct]] internal method of obj as described in 9.4.1.2.
      obj.$Construct = (thisArgument, argsList) => {
        return $BoundConstruct(realm, obj, thisArgument, argsList);
      };
    }

    // 7. Set the [[Prototype]] internal slot of obj to proto.
    obj.$Prototype = proto;

    // 8. Set the [[Extensible]] internal slot of obj to true.
    obj.setExtensible(true);

    // 9. Set the [[BoundTargetFunction]] internal slot of obj to targetFunction.
    obj.$BoundTargetFunction = targetFunction;

    // 10. Set the [[BoundThis]] internal slot of obj to the value of boundThis.
    obj.$BoundThis = boundThis;

    // 11. Set the [[BoundArguments]] internal slot of obj to boundArgs.
    obj.$BoundArguments = boundArgs;

    // 12. Return obj.
    return obj;
  }

  // ECMA262 18.2.1.1
  PerformEval(realm, x, evalRealm, strictCaller, direct) {
    // 1. Assert: If direct is false, then strictCaller is also false.
    if (direct === false) (0, _invariant2.default)(strictCaller === false, "strictCaller is only allowed on direct eval");

    // 2. If Type(x) is not String, return x.
    if (!(x instanceof _index.StringValue)) return x;

    // 3. Let script be the ECMAScript code that is the result of parsing x, interpreted as UTF-16 encoded Unicode text
    //    as described in 6.1.4, for the goal symbol Script. If the parse fails, throw a SyntaxError exception. If any
    //    early errors are detected, throw a SyntaxError or a ReferenceError exception, depending on the type of the
    //    error (but see also clause 16). Parsing and early error detection may be interweaved in an implementation
    //    dependent manner.
    let ast = (0, _parse2.default)(realm, x.value, "eval", "script");
    let script = ast.program;

    // 4. If script Contains ScriptBody is false, return undefined.
    if (!script.body) return realm.intrinsics.undefined;

    // 5. Let body be the ScriptBody of script.
    let body = t.blockStatement(script.body, script.directives);

    // 6. If strictCaller is true, let strictEval be true.
    let strictEval;
    if (strictCaller) {
      strictEval = true;
    } else {
      // 7. Else, let strictEval be IsStrict of script.
      strictEval = (0, _strict2.default)(script);
    }

    // 8. Let ctx be the running execution context. If direct is true, ctx will be the execution context that
    //    performed the direct eval. If direct is false, ctx will be the execution context for the invocation of
    //    the eval function.
    let ctx = realm.getRunningContext();

    // 9. If direct is true, then
    let lexEnv, varEnv;
    if (direct) {
      // a. Let lexEnv be NewDeclarativeEnvironment(ctx's LexicalEnvironment).
      lexEnv = _singletons.Environment.NewDeclarativeEnvironment(realm, ctx.lexicalEnvironment);

      // b. Let varEnv be ctx's VariableEnvironment.
      varEnv = ctx.variableEnvironment;
    } else {
      // 10. Else,
      // a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]).
      lexEnv = _singletons.Environment.NewDeclarativeEnvironment(realm, evalRealm.$GlobalEnv);

      // b. Let varEnv be evalRealm.[[GlobalEnv]].
      varEnv = evalRealm.$GlobalEnv;
    }

    // 11. If strictEval is true, let varEnv be lexEnv.
    if (strictEval) varEnv = lexEnv;

    // 12. If ctx is not already suspended, suspend ctx.
    ctx.suspend();

    // 13. Let evalCxt be a new ECMAScript code execution context.
    let evalCxt = new _realm.ExecutionContext();
    evalCxt.isStrict = strictEval;

    // 14. Set the evalCxt's Function to null.
    evalCxt.setFunction(null);

    // 15. Set the evalCxt's Realm to evalRealm.
    evalCxt.setRealm(evalRealm);

    // 16. Set the evalCxt's ScriptOrModule to ctx's ScriptOrModule.
    evalCxt.ScriptOrModule = ctx.ScriptOrModule;

    // 17. Set the evalCxt's VariableEnvironment to varEnv.
    evalCxt.variableEnvironment = varEnv;

    // 18. Set the evalCxt's LexicalEnvironment to lexEnv.
    evalCxt.lexicalEnvironment = lexEnv;

    // 19. Push evalCxt on to the execution context stack; evalCxt is now the running execution context.
    realm.pushContext(evalCxt);

    let result;
    try {
      // 20. Let result be EvalDeclarationInstantiation(body, varEnv, lexEnv, strictEval).
      (0, _invariant2.default)(varEnv);
      try {
        result = this.EvalDeclarationInstantiation(realm, body, varEnv, lexEnv, strictEval);
      } catch (e) {
        if (e instanceof _completions.AbruptCompletion) {
          result = e;
        } else {
          throw e;
        }
      }
      (0, _invariant2.default)(result instanceof _index.Value || result instanceof _completions.AbruptCompletion);

      // 21. If result.[[Type]] is normal, then
      if (result instanceof _index.Value) {
        // Evaluate expressions that passed for directives.
        if (script.directives) {
          for (let directive of script.directives) {
            result = new _index.StringValue(realm, directive.value.value);
          }
        }

        // a. Let result be the result of evaluating body.
        result = this.EvaluateStatements(script.body, result, strictEval, lexEnv, realm);
      }

      // 22. If result.[[Type]] is normal and result.[[Value]] is empty, then
      if (result instanceof _index.EmptyValue) {
        // a. Let result be NormalCompletion(undefined).
        result = realm.intrinsics.undefined;
      }
    } finally {
      // 23. Suspend evalCxt and remove it from the execution context stack.
      evalCxt.suspend();
      realm.popContext(evalCxt);
      realm.onDestroyScope(evalCxt.lexicalEnvironment);
    }

    // 24. Resume the context that is now on the top of the execution context stack as the running execution context.
    (0, _invariant2.default)(realm.getRunningContext() === ctx);
    ctx.resume();

    // 25. Return Completion(result).
    if (result instanceof _index.Value) {
      return result;
    } else {
      (0, _invariant2.default)(result instanceof _completions.AbruptCompletion);
      throw result;
    }
  }

  // If c is an abrupt completion and realm.savedCompletion is defined, the result is an instance of
  // JoinedAbruptCompletions and the effects that have been captured since the PossiblyNormalCompletion instance
  // in realm.savedCompletion has been created, becomes the effects of the branch that terminates in c.
  // If c is a normal completion, the result is realm.savedCompletion, with its value updated to c.
  // If c is undefined, the result is just realm.savedCompletion.
  // Call this only when a join point has been reached.
  incorporateSavedCompletion(realm, c) {
    let savedCompletion = realm.savedCompletion;
    if (savedCompletion !== undefined) {
      if (savedCompletion.savedPathConditions) {
        // Since we are joining several control flow paths, we need the curent path conditions to reflect
        // only the refinements that applied at the corresponding fork point.
        realm.pathConditions = savedCompletion.savedPathConditions;
        savedCompletion.savedPathConditions = [];
      }
      realm.savedCompletion = undefined;
      if (c === undefined) return savedCompletion;
      if (c instanceof _index.Value) {
        _singletons.Join.updatePossiblyNormalCompletionWithValue(realm, savedCompletion, c);
        return savedCompletion;
      } else {
        let e = realm.getCapturedEffects(savedCompletion);
        (0, _invariant2.default)(e !== undefined);
        realm.stopEffectCaptureAndUndoEffects(savedCompletion);
        let joined_effects = _singletons.Join.joinPossiblyNormalCompletionWithAbruptCompletion(realm, savedCompletion, c, e);
        realm.applyEffects(joined_effects);
        let jc = joined_effects[0];
        (0, _invariant2.default)(jc instanceof _completions.AbruptCompletion);
        return jc;
      }
    }
    return c;
  }

  EvaluateStatements(body, initialBlockValue, strictCode, blockEnv, realm) {
    let blockValue = initialBlockValue;
    for (let node of body) {
      if (node.type !== "FunctionDeclaration") {
        let res = blockEnv.evaluateCompletionDeref(node, strictCode);
        if (!(res instanceof _index.EmptyValue)) {
          if (res instanceof _completions.AbruptCompletion) throw (0, _index2.UpdateEmpty)(realm, res, blockValue || realm.intrinsics.empty);
          (0, _invariant2.default)(res instanceof _index.Value);
          blockValue = res;
        }
      }
    }

    // 7. Return blockValue.
    return blockValue || realm.intrinsics.empty;
  }

  PartiallyEvaluateStatements(body, blockValue, strictCode, blockEnv, realm) {
    let statementAsts = [];
    for (let node of body) {
      if (node.type !== "FunctionDeclaration") {
        let [res, nast, nio] = blockEnv.partiallyEvaluateCompletionDeref(node, strictCode);
        for (let ioAst of nio) statementAsts.push(ioAst);
        statementAsts.push(nast);
        if (!(res instanceof _index.EmptyValue)) {
          if (blockValue === undefined || blockValue instanceof _index.Value) {
            if (res instanceof _completions.AbruptCompletion) return [(0, _index2.UpdateEmpty)(realm, res, blockValue || realm.intrinsics.empty), statementAsts];
            (0, _invariant2.default)(res instanceof _completions.NormalCompletion || res instanceof _index.Value);
            blockValue = res;
          }
        }
      }
    }

    // 7. Return blockValue.
    return [blockValue || realm.intrinsics.empty, statementAsts];
  }

  // ECMA262 9.2.5
  FunctionCreate(realm, kind, ParameterList, Body, Scope, Strict, prototype) {
    // 1. If the prototype argument was not passed, then
    if (!prototype) {
      // a. Let prototype be the intrinsic object %FunctionPrototype%.
      prototype = realm.intrinsics.FunctionPrototype;
    }

    // 2. If kind is not Normal, let allocKind be "non-constructor".
    let allocKind;
    if (kind !== "normal") {
      allocKind = "non-constructor";
    } else {
      // 3. Else, let allocKind be "normal".
      allocKind = "normal";
    }

    // 4. Let F be FunctionAllocate(prototype, Strict, allocKind).
    let F = this.FunctionAllocate(realm, prototype, Strict, allocKind);

    // ECMAScript 2016, section 17:
    //   "Every other data property described in clauses 18 through 26 and in Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true } unless otherwise specified."
    // Because we call `AddRestrictedFunctionProperties` on `FunctionPrototype`, accessing property "arguments" will raise a `TypeError` by default.
    // However, in non-strict mode this behavior is not desired, so we will add them as own properties of each `FunctionValue`, in accordance with ECMA 17.
    // Note: "arguments" ***MUST NOT*** be set if the function is in strict mode or is an arrow, method, constructor, or generator function.
    //   See 16.2 "Forbidden Extensions"
    if (!Strict && kind === "normal") {
      _singletons.Properties.DefinePropertyOrThrow(realm, F, "arguments", {
        value: realm.intrinsics.undefined,
        enumerable: false,
        writable: true,
        configurable: true
      });
    }

    // 5. Return FunctionInitialize(F, kind, ParameterList, Body, Scope).
    return this.FunctionInitialize(realm, F, kind, ParameterList, Body, Scope);
  }

  // ECMA262 18.2.1.2
  EvalDeclarationInstantiation(realm, body, varEnv, lexEnv, strict) {
    // 1. Let varNames be the VarDeclaredNames of body.
    let varNames = [];
    (0, _traverseFast2.default)(body, node => {
      if (node.type === "VariableDeclaration" && node.kind === "var") {
        varNames = varNames.concat(Object.keys(t.getBindingIdentifiers(node)));
      }

      if (node.type === "FunctionExpression" || node.type === "FunctionDeclaration") {
        return true;
      }

      return false;
    });

    // 2. Let varDeclarations be the VarScopedDeclarations of body.
    let varDeclarations = this.FindVarScopedDeclarations(body);

    // 3. Let lexEnvRec be lexEnv's EnvironmentRecord.
    let lexEnvRec = lexEnv.environmentRecord;

    // 4. Let varEnvRec be varEnv's EnvironmentRecord.
    let varEnvRec = varEnv.environmentRecord;

    // 5. If strict is false, then
    if (!strict) {
      // a. If varEnvRec is a global Environment Record, then
      if (varEnvRec instanceof _environment.GlobalEnvironmentRecord) {
        // i. For each name in varNames, do
        for (let name of varNames) {
          // 1. If varEnvRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
          if (varEnvRec.HasLexicalDeclaration(name)) {
            throw realm.createErrorThrowCompletion(realm.intrinsics.SyntaxError, new _index.StringValue(realm, name + " global object is restricted"));
          }
          // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration.
        }
      }
      // b. Let thisLex be lexEnv.
      let thisLex = lexEnv;
      // c. Assert: The following loop will terminate.
      // d. Repeat while thisLex is not the same as varEnv,
      while (thisLex !== varEnv) {
        // i. Let thisEnvRec be thisLex's EnvironmentRecord.
        let thisEnvRec = thisLex.environmentRecord;
        // ii. If thisEnvRec is not an object Environment Record, then
        if (!(thisEnvRec instanceof _environment.ObjectEnvironmentRecord)) {
          // 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts.
          // 2. For each name in varNames, do
          for (let name of varNames) {
            // a. If thisEnvRec.HasBinding(name) is true, then
            if (thisEnvRec.HasBinding(name)) {
              // i. Throw a SyntaxError exception.
              throw realm.createErrorThrowCompletion(realm.intrinsics.SyntaxError, name + " global object is restricted");
              // ii. NOTE: Annex B.3.5 defines alternate semantics for the above step.
            }
            // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration.
          }
        }
        // iii. Let thisLex be thisLex's outer environment reference.
        thisLex = thisLex.parent;
        (0, _invariant2.default)(thisLex !== null);
      }
    }

    // 6. Let functionsToInitialize be a new empty List.
    let functionsToInitialize = [];

    // 7. Let declaredFunctionNames be a new empty List.
    let declaredFunctionNames = [];

    // 8. For each d in varDeclarations, in reverse list order do
    for (let d of varDeclarations.reverse()) {
      // a. If d is neither a VariableDeclaration or a ForBinding, then
      if (d.type !== "VariableDeclaration") {
        // i. Assert: d is either a FunctionDeclaration or a GeneratorDeclaration.
        (0, _invariant2.default)(d.type === "FunctionDeclaration" || d.type === "GeneratorDeclaration");
        // ii. NOTE If there are multiple FunctionDeclarations for the same name, the last declaration is used.
        // iii. Let fn be the sole element of the BoundNames of d.
        let fn = _singletons.Environment.BoundNames(realm, d)[0];
        // iv. If fn is not an element of declaredFunctionNames, then
        if (declaredFunctionNames.indexOf(fn) < 0) {
          // 1. If varEnvRec is a global Environment Record, then
          if (varEnvRec instanceof _environment.GlobalEnvironmentRecord) {
            // a. Let fnDefinable be ? varEnvRec.CanDeclareGlobalFunction(fn).
            let fnDefinable = varEnvRec.CanDeclareGlobalFunction(fn);
            // b. If fnDefinable is false, throw a TypeError exception.
            if (!fnDefinable) {
              throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, fn + " is not definable");
            }
          }
          // 2. Append fn to declaredFunctionNames.
          declaredFunctionNames.push(fn);
          // 3. Insert d as the first element of functionsToInitialize.
          functionsToInitialize.unshift(d);
        }
      }
    }

    // 9. NOTE: Annex B.3.3.3 adds additional steps at this point.

    // 10. Let declaredVarNames be a new empty List.
    let declaredVarNames = [];

    // 11. For each d in varDeclarations, do
    for (let d of varDeclarations) {
      // a. If d is a VariableDeclaration or a ForBinding, then
      if (d.type === "VariableDeclaration") {
        // i. For each String vn in the BoundNames of d, do
        for (let vn of _singletons.Environment.BoundNames(realm, d)) {
          // 1. If vn is not an element of declaredFunctionNames, then
          if (declaredFunctionNames.indexOf(vn) < 0) {
            // a. If varEnvRec is a global Environment Record, then
            if (varEnvRec instanceof _environment.GlobalEnvironmentRecord) {
              // i. Let vnDefinable be ? varEnvRec.CanDeclareGlobalVar(vn).
              let vnDefinable = varEnvRec.CanDeclareGlobalVar(vn);
              // ii. If vnDefinable is false, throw a TypeError exception.
              if (!vnDefinable) {
                throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, vn + " is not definable");
              }
            }
            // b. If vn is not an element of declaredVarNames, then
            if (declaredVarNames.indexOf(vn) < 0) {
              // i. Append vn to declaredVarNames.
              declaredVarNames.push(vn);
            }
          }
        }
      }
    }

    // 12. NOTE: No abnormal terminations occur after this algorithm step unless varEnvRec is a global Environment Record and the global object is a Proxy exotic object.

    // 13. Let lexDeclarations be the LexicallyScopedDeclarations of body.
    let lexDeclarations = [];
    for (let s of body.body) {
      if (s.type === "VariableDeclaration" && s.kind !== "var") {
        lexDeclarations.push(s);
      }
    }

    // 14. For each element d in lexDeclarations do
    for (let d of lexDeclarations) {
      // a. NOTE Lexically declared names are only instantiated here but not initialized.
      // b. For each element dn of the BoundNames of d do
      for (let dn of _singletons.Environment.BoundNames(realm, d)) {
        // c. If IsConstantDeclaration of d is true, then
        if (d.kind === "const") {
          // i. Perform ? lexEnvRec.CreateImmutableBinding(dn, true).
          lexEnvRec.CreateImmutableBinding(dn, true);
        } else {
          // d. Else,
          // i. Perform ? lexEnvRec.CreateMutableBinding(dn, false).
          lexEnvRec.CreateMutableBinding(dn, false);
        }
      }
    }

    // 15. For each production f in functionsToInitialize, do
    for (let f of functionsToInitialize) {
      // a. Let fn be the sole element of the BoundNames of f.
      let fn = _singletons.Environment.BoundNames(realm, f)[0];
      // b. Let fo be the result of performing InstantiateFunctionObject for f with argument lexEnv.
      let fo = lexEnv.evaluate(f, strict);
      (0, _invariant2.default)(fo instanceof _index.Value);
      // c. If varEnvRec is a global Environment Record, then
      if (varEnvRec instanceof _environment.GlobalEnvironmentRecord) {
        // i. Perform ? varEnvRec.CreateGlobalFunctionBinding(fn, fo, true).
        varEnvRec.CreateGlobalFunctionBinding(fn, fo, true);
      } else {
        // d. Else,
        // i. Let bindingExists be varEnvRec.HasBinding(fn).
        let bindingExists = varEnvRec.HasBinding(fn);
        // ii. If bindingExists is false, then
        if (!bindingExists) {
          // 1. Let status be ! varEnvRec.CreateMutableBinding(fn, true).
          varEnvRec.CreateMutableBinding(fn, true);
          // 2. Assert: status is not an abrupt completion because of validation preceding step 12.
          // 3. Perform ! varEnvRec.InitializeBinding(fn, fo).
          varEnvRec.InitializeBinding(fn, fo);
        } else {
          // iii. Else,
          // 1. Perform ! varEnvRec.SetMutableBinding(fn, fo, false).
          varEnvRec.SetMutableBinding(fn, fo, false);
        }
      }
    }

    // 16. For each String vn in declaredVarNames, in list order do
    for (let vn of declaredVarNames) {
      // a. If varEnvRec is a global Environment Record, then
      if (varEnvRec instanceof _environment.GlobalEnvironmentRecord) {
        // i. Perform ? varEnvRec.CreateGlobalVarBinding(vn, true).
        varEnvRec.CreateGlobalVarBinding(vn, true);
      } else {
        // b. Else,
        // i. Let bindingExists be varEnvRec.HasBinding(vn).
        let bindingExists = varEnvRec.HasBinding(vn);
        // ii. If bindingExists is false, then
        if (!bindingExists) {
          // 1. Let status be ! varEnvRec.CreateMutableBinding(vn, true).
          varEnvRec.CreateMutableBinding(vn, true);
          // 2. Assert: status is not an abrupt completion because of validation preceding step 12.
          // 3. Perform ! varEnvRec.InitializeBinding(vn, undefined).
          varEnvRec.InitializeBinding(vn, realm.intrinsics.undefined);
        }
      }
    }

    // 17. Return NormalCompletion(empty).
    return realm.intrinsics.empty;
  }

  // ECMA 9.2.10
  MakeMethod(realm, F, homeObject) {
    // Note that F is a new object, and we can thus write to internal slots
    (0, _invariant2.default)(realm.isNewObject(F));

    // 1. Assert: F is an ECMAScript function object.
    (0, _invariant2.default)(F instanceof _index.ECMAScriptSourceFunctionValue, "F is an ECMAScript function object.");

    // 2. Assert: Type(homeObject) is Object.
    (0, _invariant2.default)(homeObject instanceof _index.ObjectValue, "Type(homeObject) is Object.");

    // 3. Set the [[HomeObject]] internal slot of F to homeObject.
    F.$HomeObject = homeObject;

    // 4. Return NormalCompletion(undefined).
    return realm.intrinsics.undefined;
  }

  // ECMA 14.3.8
  DefineMethod(realm, prop, obj, env, strictCode, functionPrototype) {
    // 1. Let propKey be the result of evaluating PropertyName.
    let propKey = (0, _ObjectExpression.EvalPropertyName)(prop, env, realm, strictCode);

    // 2. ReturnIfAbrupt(propKey).

    // 3. If the function code for this MethodDefinition is strict mode code, let strict be true. Otherwise let strict be false.
    let strict = strictCode || (0, _strict2.default)(prop.body);

    // 4. Let scope be the running execution context's LexicalEnvironment.
    let scope = env;

    // 5. If functionPrototype was passed as a parameter, let kind be Normal; otherwise let kind be Method.
    let kind;
    if (functionPrototype) {
      // let kind be Normal;
      kind = "normal";
    } else {
      // otherwise let kind be Method.
      kind = "method";
    }

    // 6. Let closure be FunctionCreate(kind, StrictFormalParameters, FunctionBody, scope, strict). If functionPrototype was passed as a parameter, then pass its value as the prototype optional argument of FunctionCreate.
    let closure = this.FunctionCreate(realm, kind, prop.params, prop.body, scope, strict, functionPrototype);

    // 7. Perform MakeMethod(closure, object).
    this.MakeMethod(realm, closure, obj);

    // 8. Return the Record{[[Key]]: propKey, [[Closure]]: closure}.
    return { $Key: propKey, $Closure: closure };
  }
}
exports.FunctionImplementation = FunctionImplementation;
//# sourceMappingURL=function.js.map