UNPKG

es6-harmony

Version:

An equivalent implementation of ES6 in pure ES5 for old JavaScript engines

2,471 lines 85.3 kB
/*!
 * ES6 Harmony v0.2.4
 * This module provides an equivalent implementation of ES6(Harmony)
 * in pure ES5 and creates an ES6 environment for old browsers or
 * JavaScript engines that natively does not support ES6.
 *
 * @license Copyright (c) 2017-2018 Rousan Ali, MIT License
 *
 * Codebase: https://github.com/rousan/es6-harmony
 * Date: 28th Jan, 2018
 */

(function (global, factory) {

    "use strict";

    if (typeof module === "object" && typeof module.exports === "object") {

        // For the environment like NodeJS, CommonJS etc where module or
        // module.exports objects are available to export ES6 APIs.
        module.exports = factory(global);
    } else {

        // For browser context, where global object is window
        // and the ES6 APIs is exported as 'ES6' property of window object
        global.ES6 = factory(global);
    }

    /* window is for browser environment and global is for NodeJS environment */
})(typeof window !== "undefined" ? window : global, function (global) {

    "use strict";

    var defineProperty = Object.defineProperty;

    var defineProperties = Object.defineProperties;

    var symbolHiddenCounter = 0;

    var globalSymbolRegistry = [];

    var slice = Array.prototype.slice;

    var stringSlice = String.prototype.slice;

    var stringIndexOf = String.prototype.indexOf;

    var isArray = Array.isArray;

    var objectToString = Object.prototype.toString;

    var push = Array.prototype.push;

    var match = String.prototype.match;

    var globalIsFinite = isFinite;

    var floor = Math.floor;

    var max = Math.max;

    var min = Math.min;

    var create = Object.create;

    var stringMaxLength = Infinity;

    var symbolBucket = create(null);

    var symbolNamePattern = /^@@_____(\d+)_____$/;

    var emptyFunction = function () {};

    var simpleFunction = function (arg) {
        return arg;
    };

    var isCallable = function (fn) {
        return typeof fn === 'function';
    };

    var isConstructor = function (fn) {
        return isCallable(fn);
    };

    var Iterator = function () {};

    var ArrayIterator = function ArrayIterator(array, flag) {
        this._array = array;
        this._flag = flag;
        this._nextIndex = 0;
    };

    var StringIterator = function StringIterator(string, flag) {
        this._string = string;
        this._flag = flag;
        this._nextIndex = 0;
    };

    var MapIterator = function MapIterator(map, flag) {
        this._map = map;
        this._flag = flag;
        this._currentEntry = null;
        this._done = false;
    };

    var SetIterator = function SetIterator(set, flag) {
        this._set = set;
        this._flag = flag;
        this._currentEntry = null;
        this._done = false;
    };

    // This method will be implemented later,
    // now `false` just for development purpose
    var isES6Running = function() {
        return false;
    };

    var isObject = function (value) {
        return value !== null && (typeof value === "object" || typeof value === "function");
    };

    var addToMessageQueue = function (fn, thisArg) {
        if (!isCallable(fn))
            throw new TypeError(fn + " is not a function");
        var args = slice.call(arguments, 2);
        setTimeout(function () {
            fn.apply(thisArg, args);
        });
    };

    var es6FunctionPrototypeHasInstanceSymbol = function (instance) {
        if (typeof this !== "function")
            return false;
        return instance instanceof this;
    };

    var es6InstanceOfOperator = function instanceOf(object, constructor) {
        if (!isObject(constructor))
            throw new TypeError("Right-hand side of 'instanceof' is not an object");

        var hasInstanceSymbolProp = constructor[Symbol.hasInstance];
        if (typeof hasInstanceSymbolProp === "undefined") {
            return object instanceof constructor;
        } else if(typeof hasInstanceSymbolProp !== "function") {
            throw new TypeError(typeof hasInstanceSymbolProp + " is not a function");
        } else {
            return hasInstanceSymbolProp.call(constructor, object);
        }
    };

    // Generates name for a symbol instance and this name will be used as
    // property key for property symbols internally.
    var generateSymbolName = function (id) {
        return "@@_____" + id + "_____";
    };

    // Generates id for next Symbol instance
    var getNextSymbolId = function () {
        return symbolHiddenCounter++;
    };

    var setupSymbolInternals = function (symbol, desc) {
        defineProperties(symbol, {
            _description: {
                value: desc
            },
            _isSymbol: {
                value: true
            },
            _id: {
                value: getNextSymbolId()
            }
        });
        return symbol;
    };

    var checkSymbolInternals = function (symbol) {
        return symbol._isSymbol === true && typeof symbol._id === "number" && typeof symbol._description === "string";
    };

    var isSymbol = function (symbol) {
        return symbol instanceof Symbol && checkSymbolInternals(symbol);
    };

    var symbolFor = function (key) {
        key = String(key);
        var registryLength = globalSymbolRegistry.length,
            record,
            i = 0;

        for(; i<registryLength; ++i) {
            record = globalSymbolRegistry[i];
            if (record.key === key)
                return record.symbol;
        }

        record = {
            key: key,
            symbol: Symbol(key)
        };
        globalSymbolRegistry.push(record);
        return record.symbol;
    };

    var symbolKeyFor = function (symbol) {
        if (!ES6.isSymbol(symbol))
            throw new TypeError(String(symbol) + " is not a symbol");
        var registryLength = globalSymbolRegistry.length,
            record,
            i = 0;

        for(; i<registryLength; ++i) {
            record = globalSymbolRegistry[i];
            if (record.symbol === symbol)
                return record.key;
        }
    };

    /* It affects array1 and appends array2 at the end of array1 */
    var appendArray = function (array1, array2) {
        // Returns immediately if these are not array or not array-like objects
        if (!(typeof array1.length === "number" && array1.length >= 0 && typeof array2.length === "number" && array2.length >= 0))
            return;
        var length1 = Math.floor(array1.length),
            length2 = Math.floor(array2.length),
            i = 0;

        array1.length = length1 + length2;
        for (; i<length2; ++i)
            if (array2.hasOwnProperty(i))
                array1[length1 + i] = array2[i];
    };

    var es6ObjectPrototypeToString = function toString() {
        if (this === undefined || this === null)
            return objectToString.call(this);
        // Add support for @@toStringTag symbol
        if (typeof this[Symbol.toStringTag] === "string")
            return "[object " + this[Symbol.toStringTag] + "]";
        else
            return objectToString.call(this);
    };

    var es6ArrayPrototypeConcat = function concat() {
        if (this === undefined || this === null)
            throw new TypeError("Array.prototype.concat called on null or undefined");

        // Boxing 'this' value to wrapper object
        var self = Object(this),
            targets = slice.call(arguments),
            outputs = []; // Later it may affected by Symbol

        targets.unshift(self);

        targets.forEach(function (target) {
            // If target is primitive then just push
            if (!isObject(target))
                outputs.push(target);
            // Here Symbol.isConcatSpreadable support is added
            else if (typeof target[Symbol.isConcatSpreadable] !== "undefined") {
                if (target[Symbol.isConcatSpreadable]) {
                    appendArray(outputs, target);
                } else {
                    outputs.push(target);
                }
            } else if (isArray(target)) {
                appendArray(outputs, target);
            } else {
                outputs.push(target);
            }
        });
        return outputs;
    };

    var es6ForOfLoop = function forOf(iterable, callback, thisArg) {
        callback = typeof callback !== "function" ? emptyFunction : callback;
        if (typeof iterable[Symbol.iterator] !== "function")
            throw new TypeError("Iterable[Symbol.iterator] is not a function");
        var iterator = iterable[Symbol.iterator](),
            iterationResult;
        if (typeof iterator.next !== "function")
            throw new TypeError(".iterator.next is not a function");
        while (true) {
            iterationResult = iterator.next();
            if (!isObject(iterationResult))
                throw new TypeError("Iterator result " + iterationResult + " is not an object");
            if (iterationResult.done)
                break;
            callback.call(thisArg, iterationResult.value);
        }
    };

    // Provides simple inheritance functionality
    var simpleInheritance = function (child, parent) {
        if (typeof child !== "function" || typeof parent !== "function")
            throw new TypeError("Child and Parent must be function type");

        child.prototype = Object.create(parent.prototype);
        child.prototype.constructor = child;
    };

    // Behaves as Symbol function in ES6, take description and returns an unique object,
    // but in ES6 this function returns 'symbol' primitive typed value.
    // Its type is 'object' not 'symbol'.
    // There is no wrapping in this case i.e. Object(sym) = sym.
    var Symbol = function Symbol(desc) {
        desc = typeof desc === "undefined" ? "" : String(desc);

        if(this instanceof Symbol)
            throw new TypeError("Symbol is not a constructor");

        var newInstance = setupSymbolInternals(Object.create(Symbol.prototype), desc);
        symbolBucket[newInstance._id] = newInstance;
        return newInstance;
    };

    defineProperties(Symbol, {

        "for": {
            value: symbolFor,
            writable: true,
            configurable: true
        },

        "keyFor": {
            value: symbolKeyFor,
            writable: true,
            configurable: true
        },

        "hasInstance": {
            value: Symbol("Symbol.hasInstance")
        },

        "isConcatSpreadable": {
            value: Symbol("Symbol.isConcatSpreadable")
        },

        "iterator": {
            value: Symbol("Symbol.iterator")
        },

        "toStringTag": {
            value: Symbol("Symbol.toStringTag")
        }
    });

    // In ES6, this function returns like 'Symbol(<desc>)', but in this case
    // this function returns the symbol's internal name to work properly.
    Symbol.prototype.toString = function () {
        return generateSymbolName(this._id);
    };

    // Returns itself but in ES6 It returns 'symbol' typed value.
    Symbol.prototype.valueOf = function () {
        return this;
    };

    // Make Iterator like iterable
    defineProperty(Iterator.prototype, Symbol.iterator.toString(), {
        value: function () {return this;},
        writable: true,
        configurable: true
    });

    simpleInheritance(ArrayIterator, Iterator);

    simpleInheritance(StringIterator, Iterator);

    simpleInheritance(MapIterator, Iterator);

    simpleInheritance(SetIterator, Iterator);

    defineProperty(ArrayIterator.prototype, Symbol.toStringTag.toString(), {
        value: "Array Iterator",
        configurable: true
    });

    defineProperty(StringIterator.prototype, Symbol.toStringTag.toString(), {
        value: "String Iterator",
        configurable: true
    });

    defineProperty(MapIterator.prototype, Symbol.toStringTag.toString(), {
        value: "Map Iterator",
        configurable: true
    });

    defineProperty(SetIterator.prototype, Symbol.toStringTag.toString(), {
        value: "Set Iterator",
        configurable: true
    });

    // This iterator works on any Array or TypedArray or array-like objects
    ArrayIterator.prototype.next = function next() {
        if (!(this instanceof ArrayIterator))
            throw new TypeError("Method Array Iterator.prototype.next called on incompatible receiver " + String(this));

        var self = this,
            nextValue;

        if (self._nextIndex === -1) {
            return {
                done: true,
                value: undefined
            };
        }

        if (!(typeof self._array.length === "number" && self._array.length >= 0)) {
            self._nextIndex = -1;
            return {
                done: true,
                value: undefined
            };
        }

        // _flag = 1 for [index, value]
        // _flag = 2 for [value]
        // _flag = 3 for [index]
        if (self._nextIndex < Math.floor(self._array.length)) {
            if (self._flag === 1)
                nextValue = [self._nextIndex, self._array[self._nextIndex]];
            else if (self._flag === 2)
                nextValue = self._array[self._nextIndex];
            else if (self._flag === 3)
                nextValue = self._nextIndex;
            self._nextIndex++;
            return {
                done: false,
                value: nextValue
            };
        } else {
            self._nextIndex = -1;
            return {
                done: true,
                value: undefined
            };
        }
    };

    StringIterator.prototype.next = function next() {
        if (!(this instanceof StringIterator))
            throw new TypeError("Method String Iterator.prototype.next called on incompatible receiver " + String(this));

        var self = this,
            stringObject = new String(this._string),
            nextValue;

        if (self._nextIndex === -1) {
            return {
                done: true,
                value: undefined
            };
        }

        if (self._nextIndex < stringObject.length) {
            nextValue = stringObject[self._nextIndex];
            self._nextIndex++;
            return {
                done: false,
                value: nextValue
            };
        } else {
            self._nextIndex = -1;
            return {
                done: true,
                value: undefined
            };
        }
    };

    MapIterator.prototype.next = function next() {
        if (!(this instanceof MapIterator))
            throw new TypeError("Method Map Iterator.prototype.next called on incompatible receiver " + String(this));
        var self = this,
            nextValue;
        if (self._done) {
            return {
                done: true,
                value: undefined
            };
        }
        if (self._currentEntry === null)
            self._currentEntry = self._map._head;
        else
            self._currentEntry = self._currentEntry.next;

        if (self._currentEntry === null) {
            self._done = true;
            return {
                done: true,
                value: undefined
            };
        }
        // _flag = 1 for [key, value]
        // _flag = 2 for [value]
        // _flag = 3 for [key]
        if (self._flag === 1)
            nextValue = [self._currentEntry.key, self._currentEntry.value];
        else if (self._flag === 2)
            nextValue = self._currentEntry.value;
        else if (self._flag === 3)
            nextValue = self._currentEntry.key;
        return {
            done: false,
            value: nextValue
        };
    };

    SetIterator.prototype.next = function next() {
        if (!(this instanceof SetIterator))
            throw new TypeError("Method Set Iterator.prototype.next called on incompatible receiver " + String(this));
        var self = this,
            nextValue;
        if (self._done) {
            return {
                done: true,
                value: undefined
            };
        }
        if (self._currentEntry === null)
            self._currentEntry = self._set._head;
        else
            self._currentEntry = self._currentEntry.next;

        if (self._currentEntry === null) {
            self._done = true;
            return {
                done: true,
                value: undefined
            };
        }
        // _flag = 1 for [value, value]
        // _flag = 2 for [value]
        if (self._flag === 1)
            nextValue = [self._currentEntry.value, self._currentEntry.value];
        else if (self._flag === 2)
            nextValue = self._currentEntry.value;
        return {
            done: false,
            value: nextValue
        };
    };

    var es6ArrayPrototypeIteratorSymbol = function values() {
        if (this === undefined || this === null)
            throw new TypeError("Cannot convert undefined or null to object");

        var self = Object(this);
        return new ArrayIterator(self, 2);
    };

    var es6StringPrototypeIteratorSymbol = function values() {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype[Symbol.iterator] called on null or undefined");
        return new StringIterator(String(this), 0);
    };

    var es6ArrayPrototypeEntries = function entries() {
        if (this === undefined || this === null)
            throw new TypeError("Cannot convert undefined or null to object");

        var self = Object(this);
        return new ArrayIterator(self, 1);
    };

    var es6ArrayPrototypeKeys = function keys() {
        if (this === undefined || this === null)
            throw new TypeError("Cannot convert undefined or null to object");
        var self = Object(this);
        return new ArrayIterator(self, 3);
    };

    var SpreadOperatorImpl = function (target, thisArg) {
        this._target = target;
        this._values = [];
        this._thisArg = thisArg;
    };

    // All the arguments must be iterable
    SpreadOperatorImpl.prototype.spread = function () {
        var self = this;
        slice.call(arguments).forEach(function (iterable) {
            ES6.forOf(iterable, function (value) {
                self._values.push(value);
            });
        });
        return self;
    };

    SpreadOperatorImpl.prototype.add = function () {
        var self = this;
        slice.call(arguments).forEach(function (value) {
            self._values.push(value);
        });
        return self;
    };

    SpreadOperatorImpl.prototype.call = function (thisArg) {
        if (typeof this._target !== "function")
            throw new TypeError("Target is not a function");
        thisArg = arguments.length <= 0 ? this._thisArg : thisArg;
        return this._target.apply(thisArg, this._values);
    };

    SpreadOperatorImpl.prototype.new = function () {
        if (typeof this._target !== "function")
            throw new TypeError("Target is not a constructor");

        var temp,
            returnValue;
        temp = Object.create(this._target.prototype);
        returnValue = this._target.apply(temp, this._values);
        return isObject(returnValue) ? returnValue : temp;
    };

    // Affects the target array
    SpreadOperatorImpl.prototype.array = function () {
        if (!isArray(this._target))
            throw new TypeError("Target is not a array");
        push.apply(this._target, this._values);
        return this._target;
    };

    // Target must be Array or function
    var es6SpreadOperator = function spreadOperator(target, thisArg) {
        if (!(typeof target === "function" || isArray(target)))
            throw new TypeError("Spread operator only supports on array and function objects at this moment");
        return new SpreadOperatorImpl(target, thisArg);
    };

    /*
    var es6Match = function (regexp) {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype.match called on null or undefined");
        if (regexp === undefined || regexp === null)
            return match.call(this, new RegExp(regexp));
        regexp = Object(regexp);
        var matchSymbol = regexp[Symbol.match];
        if (typeof matchSymbol !== "undefined") {
            if (typeof matchSymbol !== "function")
                throw new TypeError(typeof matchSymbol + " is not a function");
            else {
                return matchSymbol.call(regexp, this);
            }
        } else {
            return match.call(this, new RegExp(String(regexp)));
        }
    };
    */

    var es6ArrayFrom = function from(arrayLike, mapFn, thisArg) {
        var constructor,
            i = 0,
            length,
            outputs;
        // Use the generic constructor
        constructor = !isConstructor(this) ? Array : this;
        if (arrayLike === undefined || arrayLike === null)
            throw new TypeError("Cannot convert undefined or null to object");

        arrayLike = Object(arrayLike);
        if (mapFn === undefined)
            mapFn = simpleFunction;
        else if (!isCallable(mapFn))
            throw new TypeError(mapFn + " is not a function");

        if (typeof arrayLike[Symbol.iterator] === "undefined") {
            if (!(typeof arrayLike.length === "number" && arrayLike.length >= 0)) {
                outputs = new constructor(0);
                outputs.length = 0;
                return outputs;
            }
            length = Math.floor(arrayLike.length);
            outputs = new constructor(length);
            outputs.length = length;
            for(; i < length; ++i)
                outputs[i] = mapFn.call(thisArg, arrayLike[i]);
        } else {
            outputs = new constructor();
            outputs.length = 0;
            ES6.forOf(arrayLike, function (value) {
                outputs.length++;
                outputs[outputs.length - 1] = mapFn.call(thisArg, value);
            });
        }
        return outputs;
    };

    var es6ArrayOf = function of() {
        var constructor,
            outputs,
            length,
            i = 0;
        // Use the generic constructor
        constructor = !isConstructor(this) ? Array : this;
        length = arguments.length;
        outputs = new constructor(length);
        outputs.length = length;
        for(; i < length; ++i) {
            outputs[i] = arguments[i];
        }
        return outputs;
    };

    var es6ArrayPrototypeFill = function fill(value, start, end) {
        if (this === undefined || this === null)
            throw new TypeError("Array.prototype.fill called on null or undefined");

        var self = Object(this),
            i = 0,
            length;
        if (typeof self.length === "number" && self.length >= 0) {
            length = Math.floor(self.length);
            start = start === undefined ? 0 : Math.floor(Number(start));
            end = end === undefined ? length : Math.floor(Number(end));
            start = start < 0 ? start + length : start;
            end = end < 0 ? end + length : end;
            start = start < 0 ? 0 : start;
            end = end > length ? length : end;

            for(i = start; i < end; ++i)
                self[i] = value;
        }
        return self;
    };

    var es6ArrayPrototypeFind = function find(callback, thisArg) {
        if (this === undefined || this === null)
            throw new TypeError("Array.prototype.find called on null or undefined");
        if (!isCallable(callback))
            throw new TypeError(callback + " is not a function");
        var self = Object(this),
            i = 0,
            length;
        if (typeof self.length === "number" && self.length >= 0) {
            length = Math.floor(self.length);
            for (; i < length; ++i) {
                if (callback.call(thisArg, self[i], i, self))
                    return self[i];
            }
        }
    };

    var es6ArrayPrototypeFindIndex = function findIndex(callback, thisArg) {
        if (this === undefined || this === null)
            throw new TypeError("Array.prototype.findIndex called on null or undefined");
        if (!isCallable(callback))
            throw new TypeError(callback + " is not a function");
        var self = Object(this),
            i = 0,
            length;
        if (typeof self.length === "number" && self.length >= 0) {
            length = Math.floor(self.length);
            for (; i < length; ++i) {
                if (callback.call(thisArg, self[i], i, self))
                    return i;
            }
        }
        return -1;
    };

    var es6ArrayPrototypeCopyWithin = function copyWithin(target, start) {
        if (this == undefined || this === null) {
            throw new TypeError("Array.prototype.copyWithin called on null or undefined");
        }

        var self = Object(this),
            length = self.length >>> 0,
            relativeTarget = target >> 0,
            to = relativeTarget < 0 ? max(length + relativeTarget, 0) : min(relativeTarget, length),
            relativeStart = start >> 0,
            from = relativeStart < 0 ? max(length + relativeStart, 0) : min(relativeStart, length),
            end = arguments[2],
            relativeEnd = end === undefined ? length : end >> 0,
            final = relativeEnd < 0 ? max(length + relativeEnd, 0) : min(relativeEnd, length),
            count = min(final - from, length - to),
            direction = 1;

        if (from < to && to < (from + count)) {
            direction = -1;
            from += count - 1;
            to += count - 1;
        }

        while (count > 0) {
            if (from in self) {
                self[to] = self[from];
            } else {
                delete self[to];
            }

            from += direction;
            to += direction;
            count--;
        }

        return self;
    };

    // Returns hash for primitive typed key like this:
    // undefined or null => I___toString(key)
    // number => N___toString(key)
    // string => S___toString(key)
    // boolean => B___toString(key)
    //
    // But returns null for object typed key.
    var hash = function (key) {
        // String(0) === String(-0)
        // String(NaN) === String(NaN)
        var strKey = String(key);
        if (key === undefined || key === null)
            return "I___" + strKey;
        else if (typeof key === "number")
            return "N___" + strKey;
        else if (typeof key === "string")
            return "S___" + strKey;
        else if (typeof key === "boolean")
            return "B___" + strKey;
        else
            return null; /* For object key */
    };

    var Map = function Map(iterable) {
        if (!(this instanceof Map) || isMap(this))
            throw new TypeError("Constructor Map requires 'new'");
        setupMapInternals(this);

        if (iterable !== null && iterable !== undefined) {
            ES6.forOf(iterable, function (entry) {
                if (!isObject(entry))
                    throw new TypeError("Iterator value " + entry + " is not an entry object");
                this.set(entry[0], entry[1]);
            }, this);
        }
    };

    // WARNING: This method puts a link in the key object to reduce time complexity to O(1).
    // So, after map.set(key, value) call, if the linker property of the key object is deleted
    // then map.has(key) will returns false.
    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object key).
    // Space complexity is O(n) for all cases.
    Map.prototype.set = function set(key, value) {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.set called on incompatible receiver " + this);
        var keyHash = hash(key),
            objectHash = this._objectHash,
            entry;
        if (keyHash === null) {
            if (typeof key[objectHash] === "number" && this._data.objects[key[objectHash]] instanceof MapEntry) {
                entry = this._data.objects[key[objectHash]];
                entry.value = value;
                return this;
            }
            entry = new MapEntry(key, value);
            this._data.objects.push(entry);
            defineProperty(key, objectHash.toString(), {
                value: this._data.objects.length - 1,
                configurable: true
            });
            this._size++;
        } else {
            if (this._data.primitives[keyHash] instanceof MapEntry) {
                entry = this._data.primitives[keyHash];
                entry.value = value;
                return this;
            }
            entry = new MapEntry(key, value);
            this._data.primitives[keyHash] = entry;
            this._size++;
        }

        if (this._head === null) {
            this._head = entry;
            entry.next = null;
            entry.prev = null;
        }
        if (this._tail === null)
            this._tail = this._head;
        else {
            this._tail.next = entry;
            entry.prev = this._tail;
            entry.next = null;
            this._tail = entry;
        }
        return this;
    };

    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object key).
    // Space complexity is O(1)
    Map.prototype.has = function has(key) {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.has called on incompatible receiver " + this);
        var keyHash = hash(key),
            objectHash = this._objectHash;

        if (keyHash === null)
            return typeof key[objectHash] === "number" && this._data.objects[key[objectHash]] instanceof MapEntry;
        else
            return this._data.primitives[keyHash] instanceof MapEntry;
    };

    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object key).
    // Space complexity is O(1)
    Map.prototype.get = function get(key) {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.get called on incompatible receiver " + this);
        var keyHash = hash(key),
            objectHash = this._objectHash;
        if (keyHash === null) {
            if (typeof key[objectHash] === "number" && this._data.objects[key[objectHash]] instanceof MapEntry)
                return this._data.objects[key[objectHash]].value;
        } else {
            if (this._data.primitives[keyHash] instanceof MapEntry)
                return this._data.primitives[keyHash].value;
        }
    };

    // Time complexity: O(n)
    // Space complexity is O(1)
    Map.prototype.clear = function clear() {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.clear called on incompatible receiver " + this);
        var entry;
        // Clear all primitive keys
        Object.getOwnPropertyNames(this._data.primitives).forEach(function (prop) {
            if (this._data.primitives[prop] instanceof MapEntry) {
                entry = this._data.primitives[prop];
                delete this._data.primitives[prop];
                entry.next = null;
                entry.prev = null;
            }
        }, this);

        // Clear all object keys
        Object.getOwnPropertyNames(this._data.objects).forEach(function (prop) {
            if (this._data.objects[prop] instanceof MapEntry) {
                entry = this._data.objects[prop];
                delete this._data.objects[prop];
                delete entry.key[this._objectHash];
                entry.next = null;
                entry.prev = null;
            }
        }, this);
        this._data.objects.length = 0;

        // Free head and tail MapEntry
        this._head = null;
        this._tail = null;
        this._size = 0;
    };

    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object key).
    // Space complexity is O(1)
    Map.prototype.delete = function (key) {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.delete called on incompatible receiver " + this);
        var keyHash = hash(key),
            objectHash = this._objectHash,
            entry;
        if (keyHash === null) {
            if (typeof key[objectHash] === "number" && this._data.objects[key[objectHash]] instanceof MapEntry) {
                entry = this._data.objects[key[objectHash]];
                delete this._data.objects[key[objectHash]];
                delete entry.key[objectHash];
            } else
                return false;
        } else {
            if (this._data.primitives[keyHash] instanceof MapEntry) {
                entry = this._data.primitives[keyHash];
                delete this._data.primitives[keyHash];
            } else {
                return false;
            }
        }

        if (entry.prev !== null && entry.next !== null) {
            entry.prev.next = entry.next;
            entry.next.prev = entry.prev;
            entry.next = null;
            entry.prev = null;
        } else if(entry.prev === null && entry.next !== null) {
            this._head = entry.next;
            entry.next.prev = null;
            entry.next = null;
        } if (entry.prev !== null && entry.next === null) {
            this._tail = entry.prev;
            entry.prev.next = null;
            entry.prev = null;
        } else {
            this._head = null;
            this._tail = null;
        }
        this._size--;
        return true;
    };

    defineProperty(Map.prototype, "size", {
        get: function size() {
            if (!isMap(this))
                throw new TypeError("Method Map.prototype.size called on incompatible receiver " + this);
            return this._size;
        },
        configurable: true
    });

    Map.prototype.entries = function entries() {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.entries called on incompatible receiver " + this);
        return new MapIterator(this, 1);
    };

    Map.prototype.values = function values() {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.values called on incompatible receiver " + this);
        return new MapIterator(this, 2);
    };

    Map.prototype.keys = function keys() {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.keys called on incompatible receiver " + this);
        return new MapIterator(this, 3);
    };

    Map.prototype.forEach = function forEach(callback, thisArg) {
        if (!isMap(this))
            throw new TypeError("Method Map.prototype.forEach called on incompatible receiver " + this);
        if (!isCallable(callback))
            throw new TypeError(callback + " is not a function");
        var currentEntry = this._head;
        while(currentEntry !== null) {
            callback.call(thisArg, currentEntry.value, currentEntry.key, this);
            currentEntry = currentEntry.next;
        }
    };

    Map.prototype[Symbol.iterator] = Map.prototype.entries;

    defineProperty(Map.prototype, Symbol.toStringTag.toString(), {
        value: "Map",
        configurable: true
    });

    var setupMapInternals = function (map) {
        defineProperties(map, {
            _isMap: {
                value: true
            },
            _head: {
                value: null,
                writable: true
            },
            _tail: {
                value: null,
                writable: true
            },
            _objectHash: {
                value: Symbol("Hash(map)")
            },
            _size: {
                value: 0,
                writable: true
            },
            _data: {
                value: create(null, {
                    primitives: {
                        value: create(null) /* [[Prototype]] must be null */
                    },
                    objects: {
                        value: []
                    }
                })
            }
        });
    };

    var checkMapInternals = function (map) {
        return map._isMap === true
            && (map._head === null || map._head instanceof MapEntry)
            && (map._tail === null || map._tail instanceof MapEntry)
            && ES6.isSymbol(map._objectHash)
            && typeof map._size === "number"
            && isObject(map._data)
            && isObject(map._data.primitives)
            && isArray(map._data.objects);
    };

    var isMap = function (map) {
        return map instanceof Map && checkMapInternals(map);
    };

    var MapEntry = function MapEntry(key, value) {
        this.key = key;
        this.value = value;
        this.next = null;
        this.prev = null;
    };

    var Set = function Set(iterable) {
        if (!(this instanceof Set) || isSet(this))
            throw new TypeError("Constructor Set requires 'new'");
        setupSetInternals(this);

        if (iterable !== null && iterable !== undefined) {
            ES6.forOf(iterable, function (entry) {
                this.add(entry);
            }, this);
        }
    };

    // WARNING: This method puts a link in the value object to reduce time complexity to O(1).
    // So, after set.add(value) call, if the linker property of the value object is deleted
    // then set.has(value) will returns false.
    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object value).
    // Space complexity is O(n) for all cases.
    Set.prototype.add = function add(value) {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.add called on incompatible receiver " + this);
        var valueHash = hash(value),
            objectHash = this._objectHash,
            entry;
        if (valueHash === null) {
            if (typeof value[objectHash] === "number" && this._data.objects[value[objectHash]] instanceof SetEntry) {
                // If the value is already present then just return 'this'
                return this;
            }
            entry = new SetEntry(value);
            this._data.objects.push(entry);
            defineProperty(value, objectHash.toString(), {
                value: this._data.objects.length - 1,
                configurable: true
            });
            this._size++;
        } else {
            if (this._data.primitives[valueHash] instanceof SetEntry) {
                // If the value is already present then just return 'this'
                return this;
            }
            entry = new SetEntry(value);
            this._data.primitives[valueHash] = entry;
            this._size++;
        }

        if (this._head === null) {
            this._head = entry;
            entry.next = null;
            entry.prev = null;
        }
        if (this._tail === null)
            this._tail = this._head;
        else {
            this._tail.next = entry;
            entry.prev = this._tail;
            entry.next = null;
            this._tail = entry;
        }
        return this;
    };

    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object value).
    // Space complexity is O(1)
    Set.prototype.has = function has(value) {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.has called on incompatible receiver " + this);
        var valueHash = hash(value),
            objectHash = this._objectHash;

        if (valueHash === null)
            return typeof value[objectHash] === "number" && this._data.objects[value[objectHash]] instanceof SetEntry;
        else
            return this._data.primitives[valueHash] instanceof SetEntry;
    };

    // Time complexity: O(n)
    // Space complexity is O(1)
    Set.prototype.clear = function clear() {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.clear called on incompatible receiver " + this);
        var entry;
        // Clear all primitive values
        Object.getOwnPropertyNames(this._data.primitives).forEach(function (prop) {
            if (this._data.primitives[prop] instanceof SetEntry) {
                entry = this._data.primitives[prop];
                delete this._data.primitives[prop];
                entry.next = null;
                entry.prev = null;
            }
        }, this);

        // Clear all object values
        Object.getOwnPropertyNames(this._data.objects).forEach(function (prop) {
            if (this._data.objects[prop] instanceof SetEntry) {
                entry = this._data.objects[prop];
                delete this._data.objects[prop];
                delete entry.value[this._objectHash];
                entry.next = null;
                entry.prev = null;
            }
        }, this);
        this._data.objects.length = 0;

        // Free head and tail MapEntry
        this._head = null;
        this._tail = null;
        this._size = 0;
    };

    // Time complexity: O(1) for all cases(there is no worst case, same for both primitive and object value).
    // Space complexity is O(1)
    Set.prototype.delete = function (value) {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.delete called on incompatible receiver " + this);
        var valueHash = hash(value),
            objectHash = this._objectHash,
            entry;
        if (valueHash === null) {
            if (typeof value[objectHash] === "number" && this._data.objects[value[objectHash]] instanceof SetEntry) {
                entry = this._data.objects[value[objectHash]];
                delete this._data.objects[value[objectHash]];
                delete entry.value[objectHash];
            } else
                return false;
        } else {
            if (this._data.primitives[valueHash] instanceof SetEntry) {
                entry = this._data.primitives[valueHash];
                delete this._data.primitives[valueHash];
            } else {
                return false;
            }
        }

        if (entry.prev !== null && entry.next !== null) {
            entry.prev.next = entry.next;
            entry.next.prev = entry.prev;
            entry.next = null;
            entry.prev = null;
        } else if(entry.prev === null && entry.next !== null) {
            this._head = entry.next;
            entry.next.prev = null;
            entry.next = null;
        } if (entry.prev !== null && entry.next === null) {
            this._tail = entry.prev;
            entry.prev.next = null;
            entry.prev = null;
        } else {
            this._head = null;
            this._tail = null;
        }
        this._size--;
        return true;
    };

    Set.prototype.entries = function entries() {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.entries called on incompatible receiver " + this);
        return new SetIterator(this, 1);
    };

    Set.prototype.values = function values() {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.values called on incompatible receiver " + this);
        return new SetIterator(this, 2);
    };

    Set.prototype.keys = Set.prototype.values;

    Set.prototype[Symbol.iterator] = Set.prototype.values;

    defineProperty(Set.prototype, "size", {
        get: function size() {
            if (!isSet(this))
                throw new TypeError("Method Set.prototype.size called on incompatible receiver " + this);
            return this._size;
        },
        configurable: true
    });

    Set.prototype.forEach = function forEach(callback, thisArg) {
        if (!isSet(this))
            throw new TypeError("Method Set.prototype.forEach called on incompatible receiver " + this);
        if (!isCallable(callback))
            throw new TypeError(callback + " is not a function");
        var currentEntry = this._head;
        while(currentEntry !== null) {
            callback.call(thisArg, currentEntry.value, currentEntry.value, this);
            currentEntry = currentEntry.next;
        }
    };

    defineProperty(Set.prototype, Symbol.toStringTag.toString(), {
        value: "Set",
        configurable: true
    });

    var setupSetInternals = function (set) {
        defineProperties(set, {
            _isSet: {
                value: true
            },
            _head: {
                value: null,
                writable: true
            },
            _tail: {
                value: null,
                writable: true
            },
            _objectHash: {
                value: Symbol("Hash(set)")
            },
            _size: {
                value: 0,
                writable: true
            },
            _data: {
                value: create(null, {
                    primitives: {
                        value: create(null) /* [[Prototype]] must be null */
                    },
                    objects: {
                        value: []
                    }
                })
            }
        });
    };

    var checkSetInternals = function (set) {
        return set._isSet === true
            && (set._head === null || set._head instanceof SetEntry)
            && (set._tail === null || set._tail instanceof SetEntry)
            && ES6.isSymbol(set._objectHash)
            && typeof set._size === "number"
            && isObject(set._data)
            && isObject(set._data.primitives)
            && isArray(set._data.objects);
    };

    var SetEntry = function SetEntry(value) {
        this.value = value;
        this.next = null;
        this.prev = null;
    };

    var isSet = function (set) {
        return set instanceof Set && checkSetInternals(set);
    };

    var WeakMap = function WeakMap(iterable) {
        if (!(this instanceof WeakMap) || isWeakMap(this))
            throw new TypeError("Constructor WeakMap requires 'new'");
        setupWeakMapInternals(this);

        if (iterable !== null && iterable !== undefined) {
            ES6.forOf(iterable, function (entry) {
                if (!isObject(entry))
                    throw new TypeError("Iterator value " + entry + " is not an entry object");
                this.set(entry[0], entry[1]);
            }, this);
        }
    };

    var setupWeakMapInternals = function (weakMap) {
        defineProperties(weakMap, {
            _isWeakMap: {
                value: true
            },
            _objectHash: {
                value: Symbol("Hash(weakmap)")
            },
            _values: {
                value: []
            }
        });
    };

    defineProperty(WeakMap.prototype, Symbol.toStringTag.toString(), {
        value: "WeakMap",
        configurable: true
    });

    var checkWeakMapInternals = function (weakMap) {
        return weakMap._isWeakMap === true
            && ES6.isSymbol(weakMap._objectHash)
            && isArray(weakMap._values);
    };

    var isWeakMap = function (weakMap) {
        return weakMap instanceof WeakMap && checkWeakMapInternals(weakMap);
    };

    // WARNING: This method puts a link in the key object to reduce time complexity to O(1).
    // So, after weakMap.set(key, value) call, if the linker property of the key object is deleted
    // then weakMap.has(key) will return false.
    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(n) for all cases.
    WeakMap.prototype.set = function set(key, value) {
        if (!isWeakMap(this))
            throw new TypeError("Method WeakMap.prototype.set called on incompatible receiver " + this);
        // No symbol is allowed in WeakMap as key
        if (!isObject(key) || ES6.isSymbol(key))
            throw new TypeError("Invalid value used as weak map key");
        var objectHash = this._objectHash;
        if (typeof key[objectHash] === "number" && this._values.hasOwnProperty(key[objectHash])) {
            this._values[key[objectHash]] = value;
        } else {
            this._values.push(value);
            defineProperty(key, objectHash.toString(), {
                value: this._values.length - 1,
                configurable: true
            });
        }
        return this;
    };


    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(1)
    WeakMap.prototype.get = function get(key) {
        if (!isWeakMap(this))
            throw new TypeError("Method WeakMap.prototype.get called on incompatible receiver " + this);
        if (!isObject(key) || ES6.isSymbol(key))
            return;
        var objectHash = this._objectHash;
        if (typeof key[objectHash] === "number" && this._values.hasOwnProperty(key[objectHash]))
            return this._values[key[objectHash]];
    };

    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(1)
    WeakMap.prototype.has = function has(key) {
        if (!isWeakMap(this))
            throw new TypeError("Method WeakMap.prototype.has called on incompatible receiver " + this);
        if (!isObject(key) || ES6.isSymbol(key))
            return false;
        var objectHash = this._objectHash;
        return typeof key[objectHash] === "number" && this._values.hasOwnProperty(key[objectHash]);
    };

    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(1)
    WeakMap.prototype.delete = function (key) {
        if (!isWeakMap(this))
            throw new TypeError("Method WeakMap.prototype.delete called on incompatible receiver " + this);
        if (!isObject(key) || ES6.isSymbol(key))
            return false;
        var objectHash = this._objectHash;
        if (typeof key[objectHash] === "number" && this._values.hasOwnProperty(key[objectHash])) {
            delete this._values[key[objectHash]];
            delete key[objectHash];
            return true;
        } else
            return false;
    };

    var WeakSet = function WeakSet(iterable) {
        if (!(this instanceof WeakSet) || isWeakSet(this))
            throw new TypeError("Constructor WeakSet requires 'new'");
        setupWeakSetInternals(this);

        if (iterable !== null && iterable !== undefined) {
            ES6.forOf(iterable, function (entry) {
                this.add(entry);
            }, this);
        }
    };

    var setupWeakSetInternals = function (weakSet) {
        defineProperties(weakSet, {
            _isWeakSet: {
                value: true
            },
            _objectHash: {
                value: Symbol("Hash(weakset)")
            }
        });
    };

    var checkWeakSetInternals = function (weakSet) {
        return weakSet._isWeakSet === true
            && ES6.isSymbol(weakSet._objectHash);
    };

    var isWeakSet = function (weakSet) {
        return weakSet instanceof WeakSet && checkWeakSetInternals(weakSet);
    };

    // WARNING: This method puts a link in the value object to reduce time complexity to O(1).
    // So, after weakSet.add(value) call, if the linker property of the value object is deleted
    // then weakSet.has(value) will return false.
    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(n) for all cases.
    WeakSet.prototype.add = function add(value) {
        if (!isWeakSet(this))
            throw new TypeError("Method WeakSet.prototype.add called on incompatible receiver " + this);
        // No symbol is allowed in WeakSet as value
        if (!isObject(value) || ES6.isSymbol(value))
            throw new TypeError("Invalid value used in weak set");
        var objectHash = this._objectHash;
        if (value[objectHash] === objectHash.toString()) {
            // Just ignore if the value already exists
        } else {
            defineProperty(value, objectHash.toString(), {
                value: objectHash.toString(),
                configurable: true
            });
        }
        return this;
    };

    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(1) for all cases.
    WeakSet.prototype.has = function has(value) {
        if (!isWeakSet(this))
            throw new TypeError("Method WeakSet.prototype.has called on incompatible receiver " + this);
        if (!isObject(value) || ES6.isSymbol(value))
            return false;
        var objectHash = this._objectHash;
        return value[objectHash] === objectHash.toString();
    };

    // Time complexity: O(1) for all cases(there is no worst case)
    // Space complexity is O(1) for all cases.
    WeakSet.prototype.delete = function (value) {
        if (!isWeakSet(this))
            throw new TypeError("Method WeakSet.prototype.delete called on incompatible receiver " + this);
        if (!isObject(value) || ES6.isSymbol(value))
            return false;
        var objectHash = this._objectHash;
        if (value[objectHash] === objectHash.toString()) {
            delete value[objectHash];
            return true;
        } else
            return false;
    };

    defineProperty(WeakSet.prototype, Symbol.toStringTag.toString(), {
        value: "WeakSet",
        configurable: true
    });

    var Promise = function Promise(executor) {
        if (!(this instanceof Promise) || isPromise(this))
            throw new TypeError(String(this) + " is not a promise");
        if (!isCallable(executor))
            throw new TypeError("Promise resolver " + String(executor) + " is not a function");
        setupPromiseInternals(this);
        try {
            executor((function (value) {
                this._resolve(value);
            }).bind(this), (function (reason) {
                this._reject(reason);
            }).bind(this));
        } catch (e) {
            this._reject(e);
        }
    };

    Promise.resolve = function resolve(value) {
        if (isPromise(value))
            return value;
        return new Promise(function (resolve, reject) {
            if (isThenable(value)) {
                addToMessageQueue(function () {
                    try {
                        value.then(resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
            else
                resolve(value);
        });
    };

    Promise.reject = function reject(reason) {
        return new Promise(function (resoolve, reject) {
            reject(reason);
        });
    };

    Promise.race = function race(iterable) {
        var isSettled = false;
        return new Promise(function (resolve, reject) {
            ES6.forOf(iterable, function (promise) {
                var temp1,
                    temp2;
                if (isPromise(promise)) {
                    if (isFulfilledPromise(promise)) {
                        if (!isSettled) {
                            isSettled = true;
                            addToMessageQueue(function () {
                                resolve(promise._value);
                            });
                        }
                    }
                    else if (isRejectedPromise(promise)) {
                        if (!isSettled) {
                            isSettled = true;
                            addToMessageQueue(function () {
                                reject(promise._reason);
                            });
                        }
                    } else if (isPendingPromise(promise)) {
                        temp1 = promise._resolve;
                        temp2 = promise._reject;
                        defineProperties(promise, {
                            _resolve: {
                                value: (function (value) {
                                    temp1(value);
                                    if (!isSettled) {
                                        isSettled = true;
                                        resolve(value);
                                    }
                                }).bind(promise)
                            },
                            _reject: {
                                value: (function (reason) {
                                    temp2(reason);
                                    if (!isSettled) {
                                        isSettled = true;
                                        reject(reason);
                                    }
                                }).bind(promise)
                            }
                        });
                    }
                } else if (isThenable(promise)) {
                    addToMessageQueue(function () {
                        try {
                            promise.then(function (value) {
                                if (!isSettled) {
                                    isSettled = true;
                                    resolve(value);
                                }
                            }, function (reason) {
                                if (!isSettled) {
                                    isSettled = true;
                                    reject(reason);
                                }
                            });
                        } catch (e) {
                            reject(e);
                        }
                    });
                } else {
                    if (!isSettled) {
                        isSettled = true;
                        addToMessageQueue(function () {
                            resolve(promise);
                        });
                    }
                }
            })
        });
    };

    Promise.all = function all(iterable) {
        var promises = ES6.spreadOperator([]).spread(iterable).array(),
            counter = 0,
            length = promises.length,
            values = new Array(length);

        return new Promise(function (resolve, reject) {
            if (length === 0)
                resolve(values);
            else {
                promises.forEach(function (promise, index) {
                    var temp1,
                        temp2;
                    if (isPromise(promise)) {
                        if (isFulfilledPromise(promise)) {
                            values[index] = promise._value;
                            counter++;
                            if (counter === length) {
                                addToMessageQueue(function () {
                                    resolve(values);
                                });
                            }
                        } else if(isRejectedPromise(promise)) {
                            addToMessageQueue(function () {
                                reject(promise._reason);
                            });
                        } else if(isPendingPromise(promise)) {
                            temp1 = promise._resolve;
                            temp2 = promise._reject;
                            defineProperties(promise, {
                                _resolve: {
                                    value: (function (value) {
                                        temp1(value);
                                        values[index] = value;
                                        counter++;
                                        if (counter === length) {
                                            resolve(values);
                                        }
                                    }).bind(promise)
                                },
                                _reject: {
                                    value: (function (reason) {
                                        temp2(reason);
                                        reject(reason);
                                    }).bind(promise)
                                }
                            });
                        }
                    } else if (isThenable(promise)) {
                        addToMessageQueue(function () {
                            try {
                                promise.then(function (value) {
                                    values[index] = value;
                                    counter++;
                                    if (counter === length) {
                                        resolve(values);
                                    }
                                }, function (reason) {
                                    // If the returned promise is already rejected, then it does nothing
                                    reject(reason);
                                });
                            } catch (e) {
                                reject(e);
                            }
                        });
                    } else {
                        values[index] = promise;
                        counter++;
                        if (counter === length) {
                            addToMessageQueue(function () {
                                resolve(values);
                            });
                        }
                    }
                });
            }
        });
    };

    Promise.prototype.then = function then(onFulfilled, onRejected) {
        if (!isPromise(this))
            throw new TypeError(this + " is not a promise");
        onFulfilled = !isCallable(onFulfilled) ? defaultPromiseOnFulfilled : onFulfilled;
        onRejected = !isCallable(onRejected) ? defaultPromiseOnRejected : onRejected;

        var chainedPromise = new Promise(function (resolve, reject) {}),
            nextOnFulfilled,
            nextOnRejected;

        nextOnFulfilled = function (value) {
            var result;
            try {
                result = onFulfilled(value);
                processPromiseResult(result, chainedPromise);
            } catch (e) {
                chainedPromise._reject(e);
            }
        };

        nextOnRejected = function (reason) {
            var result;
            try {
                result = onRejected(reason);
                processPromiseResult(result, chainedPromise);
            } catch (e) {
                chainedPromise._reject(e);
            }
        };

        if (isPendingPromise(this)) {
            this._onFulfilled.push(nextOnFulfilled);
            this._onRejected.push(nextOnRejected);
        } else if (isFulfilledPromise(this)) {
            addToMessageQueue(nextOnFulfilled, undefined, this._value);
        } else if (isRejectedPromise(this))
            addToMessageQueue(nextOnRejected, undefined, this._reason);
        return chainedPromise;
    };

    var processPromiseResult = function (result, chainedPromise) {
        var temp1,
            temp2;
        if (isPromise(result)) {
            if (isFulfilledPromise(result))
                chainedPromise._resolve(result._value);
            else if (isRejectedPromise(result))
                chainedPromise._reject(result._reason);
            else if (isPendingPromise(result)) {
                temp1 = result._resolve;
                temp2 = result._reject;
                defineProperties(result, {
                    _resolve: {
                        value: (function (value) {
                            temp1(value);
                            chainedPromise._resolve(value);
                        }).bind(result)
                    },
                    _reject: {
                        value: (function (reason) {
                            temp2(reason);
                            chainedPromise._reject(reason);
                        }).bind(result)
                    }
                });
            }
        } else if (isThenable(result)) {
            addToMessageQueue(function () {
                try {
                    result.then((function (value) {
                        this._resolve(value);
                    }).bind(chainedPromise), (function (reason) {
                        this._reject(reason);
                    }).bind(chainedPromise));
                } catch (e) {
                    chainedPromise._reject(e);
                }
            });
        } else
            chainedPromise._resolve(result);
    };

    Promise.prototype.catch = function (onRejected) {
        if (!isCallable(this["then"]))
            throw new TypeError("(var).then is not a function");
        return this["then"](undefined, onRejected);
    };

    defineProperty(Promise.prototype, Symbol.toStringTag.toString(), {
        value: "Promise",
        configurable: true
    });

    // Although this method is not standard i.e. is not a part of ES6,
    // but it is given for testing purpose
    Promise.prototype.toString = function () {
        if (!isPromise(this))
            throw new TypeError(this + " is not a promise");
        switch (this._state) {
            case "pending":
                return "Promise { <pending> }";
            case "fulfilled":
                return "Promise { " + this._value + " }";
            case "rejected":
                return "Promise { <rejected> " + this._reason + " }";
        }
    };

    var isThenable = function (value) {
        return isObject(value) && isCallable(value.then);
    };

    var defaultPromiseOnFulfilled = function (value) {
        return Promise.resolve(value);
    };

    var defaultPromiseOnRejected = function (reason) {
        return Promise.reject(reason);
    };

    var promiseResolve = function (value) {
        // Just return if the promise is settled already
        if (isSettledPromise(this))
            return;
        defineProperties(this, {
            _state: {
                value: "fulfilled"
            },
            _value: {
                value: value
            }
        });
        if (this._onFulfilled.length > 0) {
            addToMessageQueue(function (value) {
                this._onFulfilled.forEach(function (callback) {
                    callback(value);
                });
                // Free the references of the callbacks, because
                // these are not needed anymore after calling first time _resolve() method
                this._onFulfilled.length = 0;
                this._onRejected.length = 0;
            }, this, value);
        }
    };

    var promiseReject = function (reason) {
        // Just return if the promise is settled already
        if (isSettledPromise(this))
            return;
        defineProperties(this, {
            _state: {
                value: "rejected"
            },
            _reason: {
                value: reason
            }
        });
        if (this._onRejected.length > 0) {
            addToMessageQueue(function (reason) {
                this._onRejected.forEach(function (callback) {
                    callback(reason);
                });
                // Free the references of the callbacks, because
                // these are not needed anymore after calling first time _reject() method
                this._onFulfilled.length = 0;
                this._onRejected.length = 0;
            }, this, reason);
        }
    };

    var setupPromiseInternals = function (promise) {
        defineProperties(promise, {
            _isPromise: {
                value: true
            },
            _onFulfilled: {
                value: []
            },
            _onRejected: {
                value: []
            },
            _resolve: {
                value: promiseResolve.bind(promise),
                configurable: true
            },
            _reject: {
                value: promiseReject.bind(promise),
                configurable: true
            },
            _state: {
                value: "pending",
                configurable: true
            },
            _value: {
                value: undefined,
                configurable: true
            },
            _reason: {
                value: undefined,
                configurable: true
            }
        });
    };

    var isPendingPromise = function (promise) {
        return promise._state === "pending";
    };

    var isFulfilledPromise = function (promise) {
        return promise._state === "fulfilled";
    };

    var isRejectedPromise = function (promise) {
        return promise._state === "rejected";
    };

    var isSettledPromise = function (promise) {
        return promise._state === "fulfilled" || promise._state === "rejected";
    };

    var isValidPromiseState = function (state) {
        return ["pending", "fulfilled", "rejected"].indexOf(String(state)) !== -1;
    };

    var checkPromiseInternals = function (promise) {
        return promise._isPromise === true
            && isArray(promise._onFulfilled)
            && isArray(promise._onRejected)
            && isCallable(promise._resolve)
            && isCallable(promise._reject)
            && isValidPromiseState(promise._state)
            && promise.hasOwnProperty("_value")
            && promise.hasOwnProperty("_reason")
    };

    var isPromise = function (promise) {
        return promise instanceof Promise && checkPromiseInternals(promise);
    };

    var es6NumberIsNaN = function isNaN(value) {
        return typeof value === "number" && value !== value;
    };

    var es6NumberIsFinite = function isFinite(value) {
        return typeof value === "number" && globalIsFinite(value);
    };

    var es6NumberIsInteger = function isInteger(value) {
        return es6NumberIsFinite(value) && floor(value) === value;
    };

    var es6NumberIsSafeInteger = function isSafeInteger(value) {
        return es6NumberIsInteger(value) && Math.abs(value) <= Number.MAX_SAFE_INTEGER;
    };

    var es6ObjectIs = function is(value1, value2) {
        if (typeof value1 === "number" && typeof value2 === "number") {
            if (value1 === 0 && value2 === 0)
                return 1 / value1 === 1 / value2;
            else if (es6NumberIsNaN(value1) && es6NumberIsNaN(value2))
                return true;
            else
                return value1 === value2;
        } else
            return value1 === value2;
    };


    var es6ObjectSetPrototypeOf = function setPrototypeOf(obj, prototype) {
        if (obj === undefined || obj === null)
            throw new TypeError("Object.setPrototypeOf called on null or undefined");
        if ( !(prototype === null || isObject(prototype)) )
            throw new TypeError("Object prototype may only be an Object or null: " + String(prototype));

        var protoDesc = Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
        // If Object.prototype.__proto__ does not exist or it is
        // not an accessor property then just throw errors
        if (protoDesc === undefined || !isCallable(protoDesc.set))
            throw new TypeError("Object.prototype.__proto__ accessor property does not exist");

        protoDesc.set.call(obj, prototype);
        return obj;
    };

    var es6ObjectAssign = function assign(target) {
        if (target === null || target === undefined)
            throw new TypeError("Cannot convert undefined or null to object");
        target = Object(target);
        var i,
            j,
            keys,
            nextSource;

        for(i = 1; i < arguments.length; ++i) {
            nextSource = arguments[i];
            if (nextSource === undefined || nextSource === null)
                continue;
            nextSource = Object(nextSource);
            keys = Object.keys(nextSource);
            for (j = 0; j < keys.length; ++j) {
                target[keys[j]] = nextSource[keys[j]];
            }
        }
        return target;
    };

    var es6ObjectGetOwnPropertySymbols = function getOwnPropertySymbols(obj) {
        if (obj === null || obj === undefined)
            throw new TypeError("Cannot convert undefined or null to object");
        obj = Object(obj);
        var ownProps = Object.getOwnPropertyNames(obj),
            matched,
            sId,
            foundSymbols = [];
        ownProps.forEach(function (prop) {
            matched = prop.match(symbolNamePattern);
            if (matched === null)
                return;
            sId = matched[1];
            if (isSymbol(symbolBucket[sId]))
                foundSymbols.push(symbolBucket[sId]);
        });
        return foundSymbols;
    };

    var es6StringFromCodePoint = function fromCodePoint() {
        var i = 0,
            outChars = [],
            codePoint,
            length = arguments.length;
        for (; i < length; ++i) {
            codePoint = Number(arguments[i]);
            if ( !(Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 0x10FFFF) )
                throw new RangeError("Invalid code point " + codePoint);
            if (codePoint < 0x10000)
                outChars.push(String.fromCharCode(codePoint));
            else {
                codePoint -= 0x10000;
                outChars.push(String.fromCharCode((codePoint >> 10) + 0xD800));
                outChars.push(String.fromCharCode((codePoint % 0x400) + 0xDC00));
            }
        }
        return outChars.join("");
    };

    var es6StringPrototypeCodePointAt = function (position) {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype.codePointAt called on null or undefined");
        var self = String(this),
            first,
            second,
            length = self.length;
        position = Number(position);
        position = Number.isNaN(position) ? 0 : position;
        position = (position < 0 ? -1 : 1) * Math.floor(Math.abs(position));
        if (position >= 0 && position < length) {
            first = self.charCodeAt(position);
            if (first >= 0xD800 && first <= 0xDBFF && position + 1 < length) {
                second = self.charCodeAt(position + 1);
                if (second >= 0xDC00 && second <= 0xDFFF)
                    return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
                else
                    return first;
            } else
                return first;
        }
    };

    var es6StringPrototypeStartsWith = function startsWith(searchString, position) {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype.startsWith called on null or undefined");
        if (objectToString.call(searchString) === '[object RegExp]')
            throw new TypeError("First argument to String.prototype.startsWith must not be a regular expression");

        var self = String(this),
            searchStringLength,
            startIndex,
            i = 0,
            length = self.length;

        searchString = String(searchString);
        searchStringLength = searchString.length;

        position = Number(position);
        position = Number.isNaN(position) ? 0 : position;
        position = (position < 0 ? -1 : 1) * Math.floor(Math.abs(position));

        startIndex = min(max(position, 0), length);
        if (startIndex + searchStringLength > length)
            return false;

        for(; i < searchStringLength; ++i) {
            if ((searchString.charCodeAt(i)) !== self.charCodeAt(startIndex + i))
                return false;
        }
        return true;
    };

    var es6StringPrototypeEndsWith = function endsWith(searchString, position) {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype.endsWith called on null or undefined");
        if (objectToString.call(searchString) === '[object RegExp]')
            throw new TypeError("First argument to String.prototype.endsWith must not be a regular expression");

        var self = String(this),
            endIndex,
            searchStringLength,
            length = self.length;
        searchString = String(searchString);
        searchStringLength = searchString.length;
        position = position === undefined ? length : position;
        position = Number(position);
        position = Number.isNaN(position) ? 0 : position;
        position = (position < 0 ? -1 : 1) * Math.floor(Math.abs(position));

        endIndex = min(max(position, 0), length);
        return stringSlice.call(self, endIndex - searchStringLength, endIndex) === searchString;
    };

    var es6StringPrototypeIncludes = function includes(searchString, position) {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype.endsWith called on null or undefined");
        if (objectToString.call(searchString) === '[object RegExp]')
            throw new TypeError("First argument to String.prototype.includes must not be a regular expression");
        var self = String(this);
        return stringIndexOf.call(self, searchString, position) !== -1;
    };

    // Apply `Exponentiation by squaring` algorithm, provides fast repeat
    var repeatString = function repeatString(string, count) {
        if (count < 1) { return ""; }
        if (count % 2) { return repeatString(string, count - 1) + string; }
        var half = repeatString(string, count / 2);
        return half + half;
    };

    var es6StringPrototypeRepeat = function repeat(count) {
        if (this === undefined || this === null)
            throw new TypeError("String.prototype.repeat called on null or undefined");
        var self = String(this);
        count = Number(count);
        count = Number.isNaN(count) ? 0 : count;
        count = (count < 0 ? -1 : 1) * Math.floor(Math.abs(count));

        if (count < 0 || count >= stringMaxLength)
            throw new RangeError("Invalid count value");
        return repeatString(self, count);
    };

    // Some ES6 API can't be implemented in pure ES5, so this 'ES6' object provides
    // some equivalent functionality of these features.
    var ES6 = {

        // Checks if a JS value is a symbol
        // It can be used as equivalent api in ES6: typeof symbol === 'symbol'
        isSymbol: isSymbol,

        // Native ES5 'instanceof' operator does not support @@hasInstance symbol,
        // this method provides same functionality of ES6 'instanceof' operator.
        instanceOf: es6InstanceOfOperator,

        // This method behaves exactly same as ES6 for...of loop.
        forOf: es6ForOfLoop,

        // This method gives same functionality of the spread operator of ES6
        // It works on only functions and arrays.
        // Limitation: You can't create array like this [...iterable, , , , 33] by this method,
        // to achieve this you have to do like this [...iterable, undefined, undefined, undefined, 33]
        spreadOperator: es6SpreadOperator,

        isMap: isMap,

        isSet: isSet,

        isWeakMap: isWeakMap,

        isWeakSet: isWeakSet,

        isPromise: isPromise
    };
    
    // Apply all the patches to support ES6 in ES5.
    // If the running environment already supports ES6 then no patches will be applied,
    // just an empty object will be exported as 'ES6'.
    if (isES6Running())
        return {};
    else {
        defineProperty(global, "Symbol", {
            value: Symbol,
            writable: true,
            configurable: true
        });

        defineProperty(global, "Map", {
            value: Map,
            writable: true,
            configurable: true
        });

        defineProperty(global, "Set", {
            value: Set,
            writable: true,
            configurable: true
        });

        defineProperty(global, "WeakMap", {
            value: WeakMap,
            writable: true,
            configurable: true
        });

        defineProperty(global, "WeakSet", {
            value: WeakSet,
            writable: true,
            configurable: true
        });

        defineProperty(global, "Promise", {
            value: Promise,
            writable: true,
            configurable: true
        });

        defineProperty(Function.prototype, Symbol.hasInstance.toString(), {
            value: es6FunctionPrototypeHasInstanceSymbol
        });

        defineProperty(Object, "is", {
            value: es6ObjectIs,
            writable: true,
            configurable: true
        });

        defineProperty(Object, "setPrototypeOf", {
            value: es6ObjectSetPrototypeOf,
            writable: true,
            configurable: true
        });

        defineProperty(Object, "assign", {
            value: es6ObjectAssign,
            writable: true,
            configurable: true
        });

        defineProperty(Object, "getOwnPropertySymbols", {
            value: es6ObjectGetOwnPropertySymbols,
            writable: true,
            configurable: true
        });

        defineProperty(Object.prototype, "toString", {
            value: es6ObjectPrototypeToString,
            writable: true,
            configurable: true
        });

        defineProperty(String, "fromCodePoint", {
            value: es6StringFromCodePoint,
            writable: true,
            configurable: true
        });

        defineProperty(String.prototype, Symbol.iterator.toString(), {
            value: es6StringPrototypeIteratorSymbol,
            writable: true,
            configurable: true
        });

        defineProperty(String.prototype, "codePointAt", {
            value: es6StringPrototypeCodePointAt,
            writable: true,
            configurable: true
        });

        defineProperty(String.prototype, "startsWith", {
            value: es6StringPrototypeStartsWith,
            writable: true,
            configurable: true
        });

        defineProperty(String.prototype, "endsWith", {
            value: es6StringPrototypeEndsWith,
            writable: true,
            configurable: true
        });

        defineProperty(String.prototype, "includes", {
            value: es6StringPrototypeIncludes,
            writable: true,
            configurable: true
        });

        defineProperty(String.prototype, "repeat", {
            value: es6StringPrototypeRepeat,
            writable: true,
            configurable: true
        });

        defineProperty(Array, "from", {
            value: es6ArrayFrom,
            writable: true,
            configurable: true
        });

        defineProperty(Array, "of", {
            value: es6ArrayOf,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, Symbol.iterator.toString(), {
            value: es6ArrayPrototypeIteratorSymbol,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "entries", {
            value: es6ArrayPrototypeEntries,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "keys", {
            value: es6ArrayPrototypeKeys,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "concat", {
            value: es6ArrayPrototypeConcat,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "fill", {
            value: es6ArrayPrototypeFill,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "find", {
            value: es6ArrayPrototypeFind,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "findIndex", {
            value: es6ArrayPrototypeFindIndex,
            writable: true,
            configurable: true
        });

        defineProperty(Array.prototype, "copyWithin", {
            value: es6ArrayPrototypeCopyWithin,
            writable: true,
            configurable: true
        });

        defineProperty(Number, "isNaN", {
            value: es6NumberIsNaN,
            writable: true,
            configurable: true
        });

        defineProperty(Number, "isFinite", {
            value: es6NumberIsFinite,
            writable: true,
            configurable: true
        });

        defineProperty(Number, "isInteger", {
            value: es6NumberIsInteger,
            writable: true,
            configurable: true
        });

        defineProperty(Number, "parseInt", {
            value: parseInt,
            writable: true,
            configurable: true
        });

        defineProperty(Number, "parseFloat", {
            value: parseFloat,
            writable: true,
            configurable: true
        });

        defineProperty(Number, "isSafeInteger", {
            value: es6NumberIsSafeInteger,
            writable: true,
            configurable: true
        });

        if (typeof Number.EPSILON !== "number") {
            defineProperty(Number, "EPSILON", {
                value: 2.220446049250313e-16
            });
        }

        if (typeof Number.MAX_SAFE_INTEGER !== "number") {
            defineProperty(Number, "MAX_SAFE_INTEGER", {
                value: Math.pow(2, 53) - 1
            });
        }

        if (typeof Number.MIN_SAFE_INTEGER !== "number") {
            defineProperty(Number, "MIN_SAFE_INTEGER", {
                value: -(Math.pow(2, 53) - 1)
            });
        }
    }

    return ES6;
});