"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.URIUnescaped = exports.URIMark = exports.DecimalDigit = exports.URIAlpha = exports.URIReserved = undefined;
exports.SplitMatch = SplitMatch;
exports.RequireObjectCoercible = RequireObjectCoercible;
exports.HasSameType = HasSameType;
exports.AbstractRelationalComparison = AbstractRelationalComparison;
exports.AbstractEqualityComparison = AbstractEqualityComparison;
exports.StrictEqualityComparison = StrictEqualityComparison;
exports.StrictEqualityComparisonPartial = StrictEqualityComparisonPartial;
exports.SameValueZero = SameValueZero;
exports.SameValueZeroPartial = SameValueZeroPartial;
exports.SameValue = SameValue;
exports.SameValuePartial = SameValuePartial;
exports.SameValueNonNumber = SameValueNonNumber;
exports.SamePropertyKey = SamePropertyKey;
exports.Add = Add;
exports.InstanceofOperator = InstanceofOperator;
exports.OrdinaryHasInstance = OrdinaryHasInstance;
exports.Type = Type;
exports.SymbolDescriptiveString = SymbolDescriptiveString;
exports.UpdateEmpty = UpdateEmpty;

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

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

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

var _is = require("./is.js");

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

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

var _has = require("./has.js");

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

const URIReserved = exports.URIReserved = ";/?:@&=+$,"; /**
                                                         * 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.
                                                         */

const URIAlpha = exports.URIAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const DecimalDigit = exports.DecimalDigit = "0123456789";
const URIMark = exports.URIMark = "-_.!~*'()";
const URIUnescaped = exports.URIUnescaped = URIAlpha + DecimalDigit + URIMark;

// ECMA262 21.1.3.17.1
function SplitMatch(realm, S, q, R) {
  // 1. Assert: Type(R) is String.
  (0, _invariant2.default)(typeof R === "string", "expected a string");

  // 2. Let r be the number of code units in R.
  let r = R.length;

  // 3. Let s be the number of code units in S.
  let s = S.length;

  // 4. If q+r > s, return false.
  if (q + r > s) return false;

  // 5. If there exists an integer i between 0 (inclusive) and r (exclusive) such that the code unit at index
  //    q+i of S is different from the code unit at index i of R, return false.
  for (let i = 0; i < r; i++) {
    if (S[q + i] !== R[i]) {
      return false;
    }
  }

  // 6. Return q+r.
  return q + r;
}

// ECMA262 7.2.1
function RequireObjectCoercible(realm, arg, argLoc) {
  if (arg instanceof _index.AbstractValue && (arg.mightBeNull() || arg.mightBeUndefined())) {
    if (realm.isInPureScope()) {
      // In a pure function it is ok to throw if this happens to be null or undefined.
      return arg;
    }
    if (argLoc) {
      let error = new _errors.CompilerDiagnostic(`member expression object ${_index.AbstractValue.describe(arg)} is unknown`, argLoc, "PP0012", "FatalError");
      realm.handleError(error);
      throw new _errors.FatalError();
    }
    arg.throwIfNotConcrete();
  }
  if (arg instanceof _index.NullValue || arg instanceof _index.UndefinedValue) {
    throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "null or undefined");
  } else {
    return arg;
  }
}

function HasSameType(x, y) {
  const xType = x.getType();
  const yType = y.getType();
  return xType === yType || (xType === _index.IntegralValue || xType === _index.NumberValue) && (yType === _index.IntegralValue || yType === _index.NumberValue);
}

// ECMA262 7.2.12 Abstract Relational Comparison
function AbstractRelationalComparison(realm, x, y, LeftFirst, op) {
  let px, py;

  // 1. If the LeftFirst flag is true, then
  if (LeftFirst) {
    // a. Let px be ? ToPrimitive(x, hint Number).
    px = _singletons.To.ToPrimitiveOrAbstract(realm, x, "number");

    // b. Let py be ? ToPrimitive(y, hint Number).
    py = _singletons.To.ToPrimitiveOrAbstract(realm, y, "number");
  } else {
    // 2. Else the order of evaluation needs to be reversed to preserve left to right evaluation
    // a. Let py be ? ToPrimitive(y, hint Number).
    py = _singletons.To.ToPrimitiveOrAbstract(realm, y, "number");

    // b. Let px be ? ToPrimitive(x, hint Number).
    px = _singletons.To.ToPrimitiveOrAbstract(realm, x, "number");
  }

  if (px instanceof _index.AbstractValue || py instanceof _index.AbstractValue) {
    if (LeftFirst) {
      return _index.AbstractValue.createFromBinaryOp(realm, op, px, py);
    } else {
      return _index.AbstractValue.createFromBinaryOp(realm, op, py, px);
    }
  }

  // 3. If both px and py are Strings, then
  if (px instanceof _index.StringValue && py instanceof _index.StringValue) {
    // a. If py is a prefix of px, return false. (A String value p is a prefix of String value q if q can be the result of concatenating p and some other String r. Note that any String is a prefix of itself, because r may be the empty String.)
    if (px.value.startsWith(py.value)) return realm.intrinsics.false;

    // b. If px is a prefix of py, return true.
    if (py.value.startsWith(px.value)) return realm.intrinsics.true;

    // c. Let k be the smallest nonnegative integer such that the code unit at index k within px is different from the code unit at index k within py. (There must be such a k, for neither String is a prefix of the other.)
    let k = 0;
    while (px.value.charCodeAt(k) === py.value.charCodeAt(k)) {
      k += 1;
    }

    // d. Let m be the integer that is the code unit value at index k within px.
    let m = px.value.charCodeAt(k);

    // e. Let n be the integer that is the code unit value at index k within py.
    let n = py.value.charCodeAt(k);

    // f. If m < n, return true. Otherwise, return false.
    return m < n ? realm.intrinsics.true : realm.intrinsics.false;
  } else {
    // 4. Else,
    // a. Let nx be ? ToNumber(px). Because px and py are primitive values evaluation order is not important.
    let nx = _singletons.To.ToNumber(realm, px);

    // b. Let ny be ? ToNumber(py).
    let ny = _singletons.To.ToNumber(realm, py);

    // c. If nx is NaN, return undefined.
    if (isNaN(nx)) return realm.intrinsics.undefined;

    // d. If ny is NaN, return undefined.
    if (isNaN(ny)) return realm.intrinsics.undefined;

    // e. If nx and ny are the same Number value, return false.
    if (Object.is(nx, ny)) {
      return realm.intrinsics.false;
    }

    // f. If nx is +0 and ny is -0, return false.
    if (Object.is(nx, +0) && Object.is(ny, -0)) {
      return realm.intrinsics.false;
    }

    // g. If nx is -0 and ny is +0, return false.
    if (Object.is(nx, -0) && Object.is(ny, +0)) {
      return realm.intrinsics.false;
    }

    // h. If nx is +∞, return false.
    // i. If ny is +∞, return true.
    // j. If ny is -∞, return false.
    // k. If nx is -∞, return true.

    // i. If the mathematical value of nx is less than the mathematical value of ny —note that these
    //    mathematical values are both finite and not both zero—return true. Otherwise, return false.
    if (nx < ny) {
      return realm.intrinsics.true;
    } else {
      return realm.intrinsics.false;
    }
  }
}

// ECMA262 7.2.13
function AbstractEqualityComparison(realm, x, y, op) {
  // 1. If Type(x) is the same as Type(y), then
  if (HasSameType(x, y)) {
    // a. Return the result of performing Strict Equality Comparison x === y.
    const strictResult = StrictEqualityComparison(realm, x, y);
    return new _index.BooleanValue(realm, op === "==" ? strictResult : !strictResult);
  }

  // 2. If x is null and y is undefined, return true.
  if (x instanceof _index.NullValue && y instanceof _index.UndefinedValue) {
    return new _index.BooleanValue(realm, op === "==");
  }

  // 3. If x is undefined and y is null, return true.
  if (x instanceof _index.UndefinedValue && y instanceof _index.NullValue) {
    return new _index.BooleanValue(realm, op === "==");
  }

  // 4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  if (x instanceof _index.NumberValue && y instanceof _index.StringValue) {
    return AbstractEqualityComparison(realm, x, new _index.NumberValue(realm, _singletons.To.ToNumber(realm, y)), op);
  }

  // 5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
  if (x instanceof _index.StringValue && y instanceof _index.NumberValue) {
    return AbstractEqualityComparison(realm, new _index.NumberValue(realm, _singletons.To.ToNumber(realm, x)), y, op);
  }

  // 6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  if (x instanceof _index.BooleanValue) {
    return AbstractEqualityComparison(realm, new _index.NumberValue(realm, _singletons.To.ToNumber(realm, x)), y, op);
  }

  // 7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  if (y instanceof _index.BooleanValue) {
    return AbstractEqualityComparison(realm, x, new _index.NumberValue(realm, _singletons.To.ToNumber(realm, y)), op);
  }

  // 8. If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  if ((x instanceof _index.StringValue || x instanceof _index.NumberValue || x instanceof _index.SymbolValue) && y instanceof _index.ObjectValue) {
    const py = _singletons.To.ToPrimitiveOrAbstract(realm, y);
    if (py instanceof _index.AbstractValue) {
      return _index.AbstractValue.createFromBinaryOp(realm, "==", x, py);
    }
    return AbstractEqualityComparison(realm, x, py, op);
  }

  // 9. If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.
  if (x instanceof _index.ObjectValue && (y instanceof _index.StringValue || y instanceof _index.NumberValue || y instanceof _index.SymbolValue)) {
    const px = _singletons.To.ToPrimitiveOrAbstract(realm, x);
    if (px instanceof _index.AbstractValue) {
      return _index.AbstractValue.createFromBinaryOp(realm, "==", px, y);
    }
    return AbstractEqualityComparison(realm, px, y, op);
  }

  // 10. Return false.
  return new _index.BooleanValue(realm, op !== "==");
}

// ECMA262 7.2.14 Strict Equality Comparison
function StrictEqualityComparison(realm, x, y) {
  // 1. If Type(x) is different from Type(y), return false.
  if (!HasSameType(x, y)) {
    return false;
  }

  // 2. If Type(x) is Number, then
  if (x instanceof _index.NumberValue && y instanceof _index.NumberValue) {
    // a. If x is NaN, return false.
    if (isNaN(x.value)) return false;

    // b. If y is NaN, return false.
    if (isNaN(y.value)) return false;

    // c. If x is the same Number value as y, return true.
    // d. If x is +0 and y is -0, return true. (handled by c)
    // e. If x is -0 and y is +0, return true. (handled by c)
    if (x.value === y.value) return true;

    // f. Return false.
    return false;
  }

  // 3. Return SameValueNonNumber(x, y).
  return SameValueNonNumber(realm, x, y);
}

function StrictEqualityComparisonPartial(realm, x, y) {
  return StrictEqualityComparison(realm, x.throwIfNotConcrete(), y.throwIfNotConcrete());
}

// ECMA262 7.2.10
function SameValueZero(realm, x, y) {
  // 1. If Type(x) is different from Type(y), return false.
  if (!HasSameType(x, y)) {
    return false;
  }

  // 2. If Type(x) is Number, then
  if (x instanceof _index.NumberValue) {
    (0, _invariant2.default)(y instanceof _index.NumberValue);

    // a. If x is NaN and y is NaN, return true.
    if (isNaN(x.value) && isNaN(y.value)) return true;

    // b. If x is +0 and y is -0, return true.
    if (Object.is(x.value, +0) && Object.is(y.value, -0)) return true;

    // c. If x is -0 and y is +0, return true.
    if (Object.is(x.value, -0) && Object.is(y.value, +0)) return true;

    // d. If x is the same Number value as y, return true.
    if (x.value === y.value) return true;

    // e. Return false.
    return false;
  }

  // 3. Return SameValueNonNumber(x, y).
  return SameValueNonNumber(realm, x, y);
}

function SameValueZeroPartial(realm, x, y) {
  return SameValueZero(realm, x.throwIfNotConcrete(), y.throwIfNotConcrete());
}

// ECMA262 7.2.9
function SameValue(realm, x, y) {
  // 1. If Type(x) is different from Type(y), return false.
  if (!HasSameType(x, y)) {
    return false;
  }

  // 2. If Type(x) is Number, then
  if (x instanceof _index.NumberValue && y instanceof _index.NumberValue) {
    // a. If x is NaN and y is NaN, return true.
    if (isNaN(x.value) && isNaN(y.value)) return true;

    // b. If x is +0 and y is -0, return false.
    if (Object.is(x.value, +0) && Object.is(y.value, -0)) return false;

    // c. If x is -0 and y is +0, return false.
    if (Object.is(x.value, -0) && Object.is(y.value, +0)) return false;

    // d. If x is the same Number value as y, return true.
    if (x.value === y.value) return true;

    // e. Return false.
    return false;
  }

  // 3. Return SameValueNonNumber(x, y).
  return SameValueNonNumber(realm, x, y);
}

function SameValuePartial(realm, x, y) {
  return SameValue(realm, x.throwIfNotConcrete(), y.throwIfNotConcrete());
}

// ECMA262 7.2.11
function SameValueNonNumber(realm, x, y) {
  // 1. Assert: Type(x) is not Number.
  (0, _invariant2.default)(!(x instanceof _index.NumberValue), "numbers not allowed");

  // 2. Assert: Type(x) is the same as Type(y).
  (0, _invariant2.default)(x.getType() === y.getType(), "must be same type");

  // 3. If Type(x) is Undefined, return true.
  if (x instanceof _index.UndefinedValue) return true;

  // 4. If Type(x) is Null, return true.
  if (x instanceof _index.NullValue) return true;

  // 5. If Type(x) is String, then
  if (x instanceof _index.StringValue && y instanceof _index.StringValue) {
    // a. If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices), return true; otherwise, return false.
    return x.value === y.value;
  }

  // 6. If Type(x) is Boolean, then
  if (x instanceof _index.BooleanValue && y instanceof _index.BooleanValue) {
    // a. If x and y are both true or both false, return true; otherwise, return false.
    return x.value === y.value;
  }

  // 7. If Type(x) is Symbol, then
  if (x instanceof _index.SymbolValue) {
    // a. If x and y are both the same Symbol value, return true; otherwise, return false.
    return x === y;
  }

  // 8. Return true if x and y are the same Object value. Otherwise, return false.
  return x === y;
}

// Checks if two property keys are identical.
function SamePropertyKey(realm, x, y) {
  if (typeof x === "string" && typeof y === "string") {
    return x === y;
  }
  if (x instanceof _index.StringValue && y instanceof _index.StringValue) {
    return x.value === y.value;
  }
  if (x instanceof _index.SymbolValue && y instanceof _index.SymbolValue) {
    return x === y;
  }
  return false;
}

// ECMA262 12.8.5 Applying the Additive Operators to Numbers
function Add(realm, a, b, subtract = false) {
  // If either operand is NaN, the result is NaN.
  if (isNaN(a) || isNaN(b)) {
    return realm.intrinsics.NaN;
  }

  // The sum of two infinities of opposite sign is NaN.
  // The sum of two infinities of the same sign is the infinity of that sign.
  // The sum of an infinity and a finite value is equal to the infinite operand.
  // The sum of two negative zeroes is -0. The sum of two positive zeroes, or of two zeroes of opposite sign, is +0.
  // The sum of a zero and a nonzero finite value is equal to the nonzero operand.
  // The sum of two nonzero finite values of the same magnitude and opposite sign is +0.

  let anum = a;
  let bnum = b;

  // The - operator performs subtraction when applied to two operands of numeric type,
  // producing the difference of its operands; the left operand is the minuend and the right
  // operand is the subtrahend. Given numeric operands a and b, it is always the case that
  // a-b produces the same result as a+(-b).
  if (subtract) {
    bnum = -bnum;
  }

  return _index.IntegralValue.createFromNumberValue(realm, anum + bnum);
}

// ECMA262 12.10.4
function InstanceofOperator(realm, O, C) {
  // 1. If Type(C) is not Object, throw a TypeError exception.
  if (!C.mightBeObject()) {
    throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "Expecting a function in instanceof check");
  }

  // 2. Let instOfHandler be ? GetMethod(C, @@hasInstance).
  let instOfHandler = (0, _get.GetMethod)(realm, C, realm.intrinsics.SymbolHasInstance);

  // 3. If instOfHandler is not undefined, then
  if (!(instOfHandler instanceof _index.UndefinedValue)) {
    // a. Return ToBoolean(? Call(instOfHandler, C, « O »)).
    return _singletons.To.ToBooleanPartial(realm, (0, _call.Call)(realm, instOfHandler, C, [O]));
  }

  // 4. If IsCallable(C) is false, throw a TypeError exception.
  if ((0, _is.IsCallable)(realm, C) === false) {
    throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError, "Expecting a function in instanceof check");
  }

  // 5. Return ? OrdinaryHasInstance(C, O).
  return OrdinaryHasInstance(realm, C, O);
}

// ECMA262 7.3.19
function OrdinaryHasInstance(realm, C, O) {
  // 1. If IsCallable(C) is false, return false.
  if ((0, _is.IsCallable)(realm, C) === false) return false;
  (0, _invariant2.default)(C instanceof _index.ObjectValue);

  // 2. If C has a [[BoundTargetFunction]] internal slot, then
  if (C instanceof _index.BoundFunctionValue) {
    // a. Let BC be the value of C's [[BoundTargetFunction]] internal slot.
    let BC = C.$BoundTargetFunction;

    // b. Return ? InstanceofOperator(O, BC).
    return InstanceofOperator(realm, O, BC);
  }

  // 3. If Type(O) is not Object, return false.
  O = O.throwIfNotConcrete();
  if (!(O instanceof _index.ObjectValue)) return false;

  // 4. Let P be ? Get(C, "prototype").
  let P = (0, _get.Get)(realm, C, "prototype").throwIfNotConcrete();

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

  // 6. Repeat
  while (true) {
    // a. Let O be ? O.[[GetPrototypeOf]]().
    O = O.$GetPrototypeOf();

    // b. If O is null, return false.
    if (O instanceof _index.NullValue) return false;

    // c. If SameValue(P, O) is true, return true.
    if (SameValuePartial(realm, P, O) === true) return true;
  }

  return false;
}

//
function Type(realm, val) {
  if (val instanceof _index.UndefinedValue) {
    return "Undefined";
  } else if (val instanceof _index.NullValue) {
    return "Null";
  } else if ((0, _has.HasCompatibleType)(val, _index.BooleanValue)) {
    return "Boolean";
  } else if ((0, _has.HasCompatibleType)(val, _index.StringValue)) {
    return "String";
  } else if ((0, _has.HasCompatibleType)(val, _index.SymbolValue)) {
    return "Symbol";
  } else if ((0, _has.HasCompatibleType)(val, _index.IntegralValue)) {
    return "Number";
  } else if ((0, _has.HasCompatibleType)(val, _index.NumberValue)) {
    return "Number";
  } else if (!val.mightNotBeObject()) {
    return "Object";
  } else {
    (0, _invariant2.default)(val instanceof _index.AbstractValue);
    _index.AbstractValue.reportIntrospectionError(val);
    throw new _errors.FatalError();
  }
}

// ECMA262 19.4.3.2.1
function SymbolDescriptiveString(realm, sym) {
  // 1. Assert: Type(sym) is Symbol.
  (0, _invariant2.default)(sym instanceof _index.SymbolValue, "expected symbol");

  // 2. Let desc be sym's [[Description]] value.
  let desc = sym.$Description;

  // 3. If desc is undefined, let desc be the empty string.
  if (!desc) desc = "";else desc = desc.throwIfNotConcreteString().value;

  // 4. Assert: Type(desc) is String.
  (0, _invariant2.default)(typeof desc === "string", "expected string");

  // 5. Return the result of concatenating the strings "Symbol(", desc, and ")".
  return `Symbol(${desc})`;
}

// ECMA262 6.2.2.5
function UpdateEmpty(realm, completionRecord, value) {
  // 1. Assert: If completionRecord.[[Type]] is either return or throw, then completionRecord.[[Value]] is not empty.
  if (completionRecord instanceof _completions.ReturnCompletion || completionRecord instanceof _completions.ThrowCompletion) {
    (0, _invariant2.default)(completionRecord.value, "expected completion record to have a value");
  }

  // 2. If completionRecord.[[Value]] is not empty, return Completion(completionRecord).
  if (completionRecord instanceof _index.EmptyValue) return value;
  if (completionRecord instanceof _index.Value || completionRecord.value && !(completionRecord.value instanceof _index.EmptyValue)) return completionRecord;

  // 3. Return Completion{[[Type]]: completionRecord.[[Type]], [[Value]]: value, [[Target]]: completionRecord.[[Target]] }.'
  completionRecord.value = value;
  return completionRecord;
}
//# sourceMappingURL=abstract.js.map