"use strict";

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

exports.default = function (ast, strictCode, env, realm) {
  strictCode = (0, _strict2.default)(ast);

  GlobalDeclarationInstantiation(realm, ast, env, strictCode);

  let val;

  for (let node of ast.body) {
    if (node.type !== "FunctionDeclaration") {
      let res = env.evaluateCompletionDeref(node, strictCode);
      if (res instanceof _completions.AbruptCompletion) {
        if (!realm.useAbstractInterpretation) throw res;
        // We are about the leave this program 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.
        res = _singletons.Functions.incorporateSavedCompletion(realm, res);
        // The call to incorporateSavedCompletion above, has taken care of the join because res is abrupt.
        // What remains to be done is to emit throw statements to the generator.
        let generator = realm.generator;
        (0, _invariant2.default)(generator !== undefined);
        if (res instanceof _completions.JoinedAbruptCompletions) {
          generator.emitConditionalThrow(res.joinCondition, res.consequent, res.alternate);
          res = res.value;
        } else if (res instanceof _completions.ThrowCompletion) {
          generator.emitThrow(res.value);
          res = realm.intrinsics.undefined;
        } else {
          (0, _invariant2.default)(false); // other kinds of abrupt completions should not get this far
        }
      }
      if (!(res instanceof _index.EmptyValue)) {
        val = res;
      }
    }
  }
  let directives = ast.directives;
  if (!val && directives && directives.length) {
    let directive = directives[directives.length - 1];
    val = env.evaluate(directive, strictCode);
    (0, _invariant2.default)(val instanceof _index.Value);
  }

  // We are about to leave this program and this presents a join point where all control flows
  // converge into a single flow and the joined effects become the final state.
  if (val instanceof _index.Value) {
    val = _singletons.Functions.incorporateSavedCompletion(realm, val);
    if (val instanceof _completions.PossiblyNormalCompletion) {
      // There are still some conditional throws to emit and state still has to be joined in.
      // Get state to be joined in
      let e = realm.getCapturedEffects(val);
      (0, _invariant2.default)(e !== undefined);
      realm.stopEffectCaptureAndUndoEffects(val);
      let joinedEffects = _singletons.Join.joinPossiblyNormalCompletionWithAbruptCompletion(realm, val, new _completions.ReturnCompletion(realm.intrinsics.undefined), e);
      // apply joined state because the serializer expects a single end program-state. It will
      // handle conditionally emitting the abrupt and normal paths
      realm.applyEffects(joinedEffects);
      // The global state has now been updated to the join of all the flows reaching this join point
      let generator = realm.generator;
      (0, _invariant2.default)(generator !== undefined);
      generator.emitConditionalThrow(val.joinCondition, val.consequent, val.alternate);
      val = val.value;
    }
  } else {
    // program was empty. Nothing to do.
  }

  (0, _invariant2.default)(val === undefined || val instanceof _index.Value);
  return val || realm.intrinsics.empty;
};

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

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

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

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

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

var _strict2 = _interopRequireDefault(_strict);

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

var _invariant2 = _interopRequireDefault(_invariant);

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

var _traverseFast2 = _interopRequireDefault(_traverseFast);

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

// ECMA262 15.1.11
function GlobalDeclarationInstantiation(realm, ast, env, strictCode) {
  realm.getRunningContext().isStrict = realm.isStrict = strictCode;

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

  // 2. Assert: envRec is a global Environment Record.
  (0, _invariant2.default)(envRec instanceof _environment.GlobalEnvironmentRecord, "expected global environment record");

  // 3. Let lexNames be the LexicallyDeclaredNames of script.
  let lexNames = [];

  // 4. Let varNames be the VarDeclaredNames of script.
  let varNames = [];

  (0, _traverseFast2.default)(ast, node => {
    if (node.type === "VariableDeclaration") {
      if (node.kind === "var") {
        varNames = varNames.concat(_singletons.Environment.BoundNames(realm, node));
      } else {
        lexNames = lexNames.concat(_singletons.Environment.BoundNames(realm, node));
      }
    } else if (node.type === "FunctionExpression" || node.type === "FunctionDeclaration") {
      return true;
    }
    return false;
  });

  // 5. For each name in lexNames, do
  for (let name of lexNames) {
    // a. If envRec.HasVarDeclaration(name) is true, throw a SyntaxError exception.
    if (envRec.HasVarDeclaration(name)) {
      throw realm.createErrorThrowCompletion(realm.intrinsics.SyntaxError, name + " already declared with var");
    }

    // b. If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
    if (envRec.HasLexicalDeclaration(name)) {
      throw realm.createErrorThrowCompletion(realm.intrinsics.SyntaxError, name + " already declared with let or const");
    }

    // c. Let hasRestrictedGlobal be ? envRec.HasRestrictedGlobalProperty(name).
    let hasRestrictedGlobal = envRec.HasRestrictedGlobalProperty(name);

    // d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
    if (hasRestrictedGlobal) {
      throw realm.createErrorThrowCompletion(realm.intrinsics.SyntaxError, name + " global object is restricted");
    }
  }

  // 6. For each name in varNames, do
  for (let name of varNames) {
    // a. If envRec.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
    if (envRec.HasLexicalDeclaration(name)) {
      throw realm.createErrorThrowCompletion(realm.intrinsics.SyntaxError, name + " already declared with let or const");
    }
  }

  // 7. Let varDeclarations be the VarScopedDeclarations of script.
  let varDeclarations = _singletons.Functions.FindVarScopedDeclarations(ast);

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

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

  // 10. 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", "expected function");

      // 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. Let fnDefinable be ? envRec.CanDeclareGlobalFunction(fn).
        let fnDefinable = envRec.CanDeclareGlobalFunction(fn);

        // 2. If fnDefinable is false, throw a TypeError exception.
        if (!fnDefinable) {
          throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, fn + ": global function declarations are not allowed");
        }

        // 3. Append fn to declaredFunctionNames.
        declaredFunctionNames.push(fn);

        // 4. Insert d as the first element of functionsToInitialize.
        functionsToInitialize.unshift(d);
      }
    }
  }

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

  // 12. 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)) {
        // ii. If vn is not an element of declaredFunctionNames, then
        if (declaredFunctionNames.indexOf(vn) < 0) {
          // 1. Let vnDefinable be ? envRec.CanDeclareGlobalVar(vn).
          let vnDefinable = envRec.CanDeclareGlobalVar(vn);

          // 2. If vnDefinable is false, throw a TypeError exception.
          if (!vnDefinable) {
            throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, vn + ": global variable declarations are not allowed");
          }

          // 3. If vn is not an element of declaredVarNames, then
          if (declaredVarNames.indexOf(vn) < 0) {
            // a. Append vn to declaredVarNames.
            declaredVarNames.push(vn);
          }
        }
      }
    }
  }

  // 13. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. However, if the global object is a Proxy exotic object it may exhibit behaviours that cause abnormal terminations in some of the following steps.

  // 14. NOTE: Annex B.3.3.2 adds additional steps at this point.

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

  // 16. 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)) {
      // i. If IsConstantDeclaration of d is true, then
      if (d.kind === "const") {
        // 1. Perform ? envRec.CreateImmutableBinding(dn, true).
        envRec.CreateImmutableBinding(dn, true);
      } else {
        // ii. Else,
        // 1. Perform ? envRec.CreateMutableBinding(dn, false).
        envRec.CreateMutableBinding(dn, false);
      }
    }
  }

  // 17. 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 env.
    let fo = env.evaluate(f, strictCode);
    (0, _invariant2.default)(fo instanceof _index.Value);

    // c. Perform ? envRec.CreateGlobalFunctionBinding(fn, fo, false).
    envRec.CreateGlobalFunctionBinding(fn, fo, false);
  }

  // 18. For each String vn in declaredVarNames, in list order do
  for (let vn of declaredVarNames) {
    // a. Perform ? envRec.CreateGlobalVarBinding(vn, false).
    envRec.CreateGlobalVarBinding(vn, false);
  }

  // 19. Return NormalCompletion(empty).
  return realm.intrinsics.empty;
} /**
   * 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.
   */
//# sourceMappingURL=Program.js.map