-
Notifications
You must be signed in to change notification settings - Fork 13
/
polyfill.js
162 lines (158 loc) · 6.49 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
// @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
;(() => {
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 = end > start
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) {
if (inclusiveEnd) {
if (currentYieldingValue > end) return
} else {
if (currentYieldingValue >= end) return
}
} else {
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: start is number
if (typeof end !== "number") throw new TypeError() // @ts-ignore
zero = 0 // @ts-ignore
one = 1
} else {
// Assert: end is 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 > 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)
}
})()