-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathpolyfill.js
195 lines (191 loc) · 7.84 KB
/
polyfill.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// @ts-check
/// <reference path="./global.d.ts" />
console.warn(`This is an experimental implementation of the range proposal (https://github.com/tc39/proposal-Number.range) of ECMAScript.
It _will_ be changed if the specification has changed.
It should only be used to collect developers feedback about the APIs.`)
// This polyfill requires: globalThis, BigInt, private fields
;(() => {
/**
* @param {*} condition
* @returns {asserts condition}
*/
function assert(condition) {
if (!condition) throw new SpecAssertError()
}
class SpecAssertError extends Error {}
const SpecValue = {
BigIntRange: Symbol(),
NumberRange: Symbol(),
}
const generatorPrototype = Object.getPrototypeOf(Object.getPrototypeOf((function* () {})()))
const origNext = generatorPrototype.next
/** @type {(o: any) => boolean} */
let isRangeIterator
if (Object.getOwnPropertyDescriptor(generatorPrototype, "next").writable) {
generatorPrototype.next = new Proxy(origNext, {
apply(target, thisArg, args) {
if (isRangeIterator(thisArg)) throw new TypeError()
return Reflect.apply(target, thisArg, args)
},
})
}
/**
* @template {number | bigint} T
* @param {T} start
* @param {T | number | undefined} end
* @param {T} step
* @param {boolean} inclusiveEnd
* @param {T} zero
* @param {T} one
*/
function* NumericRangeIteratorObject(start, end, step, inclusiveEnd, zero, one) {
let ifIncrease
if (end === Infinity) ifIncrease = true
else if (end === -Infinity) ifIncrease = false
else {
assert(typeof start === typeof end)
if (end > start) ifIncrease = true
else ifIncrease = false
}
let ifStepIncrease = step > zero
if (ifIncrease !== ifStepIncrease) return
let hitsEnd = false
let currentCount = zero
while (hitsEnd === false) {
// @ts-ignore
let currentYieldingValue = start + step * currentCount
if (currentYieldingValue === end) hitsEnd = true // @ts-ignore
currentCount = currentCount + one
// ifIncrease && inclusiveEnd && currentYieldingValue > end
if (ifIncrease) {
assert(
((end === Infinity || typeof end === "bigint") && typeof currentYieldingValue === "bigint") ||
(typeof end === "number" && typeof currentYieldingValue === "number"),
)
if (end !== Infinity) {
if (inclusiveEnd) {
if (currentYieldingValue > end) return
} else {
if (currentYieldingValue >= end) return
}
}
} else {
assert(
((end === -Infinity || typeof end === "bigint") && typeof currentYieldingValue === "bigint") ||
(typeof end === "number" && typeof currentYieldingValue === "number"),
)
if (end !== -Infinity) {
if (inclusiveEnd) {
if (end > currentYieldingValue) return
} else {
if (end >= currentYieldingValue) return
}
}
}
yield currentYieldingValue
}
return undefined
}
/**
* @param {Parameters<typeof NumericRangeIteratorObject>} args
*/
function CreateNumericRangeIteratorWithInternalSlot(...args) {
const g = NumericRangeIteratorObject(...args)
Reflect.setPrototypeOf(g, new.target.prototype)
return g
}
/**
* @template {number | bigint} T
*/ // @ts-ignore
class NumericRangeIterator extends CreateNumericRangeIteratorWithInternalSlot {
/**
* @param {T} start
* @param {T | number | undefined} end
* @param {T | undefined | null | { step?: T, inclusive?: boolean }} optionOrStep
* @param {(typeof SpecValue)[keyof typeof SpecValue]} type
*/ // @ts-ignore
constructor(start, end, optionOrStep, type) {
if (isNaN(start) || isNaN(end)) throw new RangeError()
/** @type {T} */ let zero
/** @type {T} */ let one
if (type === SpecValue.NumberRange) {
assert(typeof start === "number")
if (typeof end !== "number") throw new TypeError() // @ts-ignore
zero = 0 // @ts-ignore
one = 1
} else {
assert(typeof start === "bigint")
// Allowing all kinds of number (number, bigint, decimals, ...) to range from a finite number to infinity.
if (!isInfinity(end) && typeof end !== "bigint") throw new TypeError() // @ts-expect-error
zero = 0n // @ts-expect-error
one = 1n
}
if (isInfinity(start)) throw RangeError()
let inclusiveEnd = false
/** @type {T} */ let step
if (optionOrStep === undefined || optionOrStep === null) step = undefined
else if (typeof optionOrStep === "object") {
step = optionOrStep.step
inclusiveEnd = Boolean(optionOrStep.inclusive)
} //
else if (type === SpecValue.NumberRange && typeof optionOrStep === "number") step = optionOrStep
else if (type === SpecValue.BigIntRange && typeof optionOrStep === "bigint") step = optionOrStep
else throw new TypeError()
if (isNaN(step)) throw new RangeError()
if (step === undefined || step === null) {
if (end === Infinity)
step = one // @ts-ignore
else if (end === -Infinity) step = -one
else {
assert(typeof end === typeof start)
if (end > start)
step = one // @ts-ignore
else step = -one
}
}
if (type === SpecValue.NumberRange && typeof step !== "number") throw new TypeError()
if (type === SpecValue.BigIntRange && typeof step !== "bigint") throw new TypeError()
if (isInfinity(step)) throw RangeError()
if (step === zero && start !== end) throw new RangeError()
const obj = super(start, end, step, inclusiveEnd, zero, one) // @ts-ignore
return obj
}
#brandCheck
next() {
this.#brandCheck
return origNext.call(this)
}
static {
isRangeIterator = (o) => #brandCheck in o
}
}
const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))
Object.setPrototypeOf(NumericRangeIterator.prototype, IteratorPrototype)
Object.defineProperty(NumericRangeIterator.prototype, Symbol.toStringTag, {
writable: false,
enumerable: false,
configurable: true,
value: "NumericRangeIterator",
})
Object.defineProperty(Iterator, "range", {
configurable: true,
writable: true,
value: (start, end, optionOrStep) => {
if (typeof start === "number")
return new NumericRangeIterator(start, end, optionOrStep, SpecValue.NumberRange)
if (typeof start === "bigint")
return new NumericRangeIterator(start, end, optionOrStep, SpecValue.BigIntRange)
throw new TypeError("Iterator.range only supports number and bigint.")
},
})
function isInfinity(x) {
if (typeof x !== "number") return false
if (Number.isNaN(x)) return false
if (Number.isFinite(x)) return false
return true
}
function isNaN(x) {
if (typeof x !== "number") return false
return Number.isNaN(x)
}
})()