diff --git a/README.md b/README.md index 2907cef..8772526 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Number.range & BigInt.range +# Iterator.range **Champions**: Jack Works @@ -6,7 +6,7 @@ **Stage**: 1 -This proposal describes adding a `Number.range` and a `BigInt.range` to JavaScript. +This proposal describes adding a `Iterator.range` to the JavaScript. See the rendered spec at [here](https://tc39.es/proposal-Number.range/). @@ -16,9 +16,9 @@ See the rendered spec at [here](https://tc39.es/proposal-Number.range/). ## Polyfill -- A polyfill is available in the [core-js](https://github.com/zloirock/core-js) library. You can find it in the [ECMAScript proposals section](https://github.com/zloirock/core-js/#numberrange). +- A polyfill is available in the [core-js](https://github.com/zloirock/core-js) library. You can find it in the [ECMAScript proposals section](https://github.com/zloirock/core-js/#numberrange). -- In the proposal repo is available a [a step-to-step implementation of the proposal](./polyfill.js) [![codecov](https://codecov.io/gh/tc39/proposal-Number.range/branch/main/graph/badge.svg)](https://codecov.io/gh/tc39/proposal-Number.range) so you can verify if there is a bug in the specification by the debugger. +- In the proposal repo is available a [a step-to-step implementation of the proposal](./polyfill.js) [![codecov](https://codecov.io/gh/tc39/proposal-Number.range/branch/main/graph/badge.svg)](https://codecov.io/gh/tc39/proposal-Number.range) so you can verify if there is a bug in the specification by the debugger. ## Motivation @@ -48,14 +48,14 @@ Tons of libraries providing a range: math.js, lodash, underscore.js, ramda, d3, - - - Decimal step (0, 0.2, 0.4, ...) - BigInt Support - - Same as Arithmetic Sequence -- Infinite Sequence `Number.range(0, Infinity)` -> (0, 1, 2, 3, ...) +- Infinite Sequence `Iterator.range(0, Infinity)` -> (0, 1, 2, 3, ...) ### Non-goals - New Syntax - String Sequence (a, b, c, d, ...) - Magic -- - E.g. `if (x in Number.range(0, 10))` (Kotlin have this feature) +- - E.g. `if (x in Iterator.range(0, 10))` (Kotlin have this feature) ### Discussions @@ -79,21 +79,21 @@ See [tests](./__tests__/test.js) to learn about more usages. ```js for (const i of BigInt.range(0n, 43n)) console.log(i) // 0n to 42n -// With iterator helper proposal -Number.range(0, Infinity) +Iterator.range(0, Infinity) .take(1000) .filter((x) => !(x % 3)) .toArray() function* even() { - for (const i of Number.range(0, Infinity)) if (i % 2 === 0) yield i + for (const i of Iterator.range(0, Infinity)) if (i % 2 === 0) yield i } -;[...Number.range(1, 100, 2)] // odd number from 1 to 99 +;[...Iterator.range(1, 100, 2)] // odd number from 1 to 99 ``` # Presentations -- 2020 Apr [Notes](https://github.com/tc39/notes/blob/main/meetings/2020-03/april-1.md#numberrange-and-bigintrange-for-stage-1) [Slides](https://docs.google.com/presentation/d/1JD9SrOEtGEviPYJ3LQGKRqDHYeF-EIt7RHB92hKPWzo/) +- [Apr 2020 Notes](https://github.com/tc39/notes/blob/main/meetings/2020-03/april-1.md#numberrange-and-bigintrange-for-stage-1) / [Slides](https://docs.google.com/presentation/d/1JD9SrOEtGEviPYJ3LQGKRqDHYeF-EIt7RHB92hKPWzo/) +- [Jul 2020 Notes](https://github.com/tc39/notes/blob/65a82252aa14c273082e7687c6712bb561bc087a/meetings/2020-07/july-22.md#numberrange-for-stage-2) / [Slides](https://docs.google.com/presentation/d/116FDDK2klJoEL8s2Q7UXiDApC681N-Q9SwpC0toAzTU/) ### Signature diff --git a/__tests__/test.js b/__tests__/test.js index 31d11d6..1987de9 100644 --- a/__tests__/test.js +++ b/__tests__/test.js @@ -1,29 +1,29 @@ /// -require("../polyfill.js") require("core-js/proposals/iterator-helpers") +require("../polyfill.js") -test("Number.range", () => { - expect(that(Number.range(-1, 5))).toMatchInlineSnapshot(`"-1f, 0f, 1f, 2f, 3f, 4f"`) - expect(that(Number.range(-5, 1))).toMatchInlineSnapshot(`"-5f, -4f, -3f, -2f, -1f, 0f"`) - expect(that(Number.range(0, 1, 0.1))).toMatchInlineSnapshot( +test("Iterator.range", () => { + expect(that(Iterator.range(-1, 5))).toMatchInlineSnapshot(`"-1f, 0f, 1f, 2f, 3f, 4f"`) + expect(that(Iterator.range(-5, 1))).toMatchInlineSnapshot(`"-5f, -4f, -3f, -2f, -1f, 0f"`) + expect(that(Iterator.range(0, 1, 0.1))).toMatchInlineSnapshot( `"0f, 0.1f, 0.2f, 0.30000000000000004f, 0.4f, 0.5f, 0.6000000000000001f, 0.7000000000000001f, 0.8f, 0.9f"` ) - expect(that(Number.range(2 ** 53 - 1, 2 ** 53, { inclusive: true }))).toMatchInlineSnapshot( + expect(that(Iterator.range(2 ** 53 - 1, 2 ** 53, { inclusive: true }))).toMatchInlineSnapshot( `"9007199254740991f, 9007199254740992f"` ) - expect(that(Number.range(0, 0))).toMatchInlineSnapshot(`""`) - expect(that(Number.range(0, -5, 1))).toMatchInlineSnapshot(`""`) + expect(that(Iterator.range(0, 0))).toMatchInlineSnapshot(`""`) + expect(that(Iterator.range(0, -5, 1))).toMatchInlineSnapshot(`""`) }) -test("BigInt.range", () => { - expect(that(BigInt.range(-1n, 5n))).toMatchInlineSnapshot(`"-1n, 0n, 1n, 2n, 3n, 4n"`) - expect(that(BigInt.range(-5n, 1n))).toMatchInlineSnapshot(`"-5n, -4n, -3n, -2n, -1n, 0n"`) +test("Iterator.range", () => { + expect(that(Iterator.range(-1n, 5n))).toMatchInlineSnapshot(`"-1n, 0n, 1n, 2n, 3n, 4n"`) + expect(that(Iterator.range(-5n, 1n))).toMatchInlineSnapshot(`"-5n, -4n, -3n, -2n, -1n, 0n"`) }) test("Range to infinity", () => { { let q = 0 - for (const i of Number.range(0, Infinity)) { + for (const i of Iterator.range(0, Infinity)) { q += i if (i >= 100) break } @@ -31,7 +31,7 @@ test("Range to infinity", () => { } { let q = 0n - for (const i of BigInt.range(0n, Infinity, { inclusive: true, step: 2n })) { + for (const i of Iterator.range(0n, Infinity, { inclusive: true, step: 2n })) { q += i if (i >= 100) break } @@ -40,61 +40,33 @@ test("Range to infinity", () => { }) test("Use with Iterator helpers", () => { - expect(that(Number.range(0, 10).take(5))).toMatchInlineSnapshot(`"0f, 1f, 2f, 3f, 4f"`) - expect(that(Number.range(0, 10).map((x) => x * 2))).toMatchInlineSnapshot( + expect(that(Iterator.range(0, 10).take(5))).toMatchInlineSnapshot(`"0f, 1f, 2f, 3f, 4f"`) + expect(that(Iterator.range(0, 10).map((x) => x * 2))).toMatchInlineSnapshot( `"0f, 2f, 4f, 6f, 8f, 10f, 12f, 14f, 16f, 18f"` ) - expect(BigInt.range(0n, 10n).reduce((prev, curr) => prev + curr, 0n)).toMatchInlineSnapshot(`45n`) + expect(Iterator.range(0n, 10n).reduce((prev, curr) => prev + curr, 0n)).toMatchInlineSnapshot(`45n`) }) test("Be an iterator", () => { - const x = Number.range(0, 10) + const x = Iterator.range(0, 10) const iteratorPrototype = (function* () {})().__proto__.__proto__.__proto__ expect(x.__proto__.__proto__ === iteratorPrototype).toBeTruthy() }) test("NaN", () => { - expect(that(Number.range(NaN, 0))).toMatchInlineSnapshot(`""`) - expect(that(Number.range(0, NaN))).toMatchInlineSnapshot(`""`) - expect(that(Number.range(NaN, NaN))).toMatchInlineSnapshot(`""`) - - expect(that(Number.range(0, 0, { step: NaN }))).toMatchInlineSnapshot(`""`) - expect(that(Number.range(0, 5, NaN))).toMatchInlineSnapshot(`""`) -}) + expect(that(Iterator.range(NaN, 0))).toMatchInlineSnapshot(`""`) + expect(that(Iterator.range(0, NaN))).toMatchInlineSnapshot(`""`) + expect(that(Iterator.range(NaN, NaN))).toMatchInlineSnapshot(`""`) -test("{from, to, step} getter", () => { - { - const a = Number.range(1, 3) - expect(a.start).toMatchInlineSnapshot(`1`) - expect(a.end).toMatchInlineSnapshot(`3`) - expect(a.step).toMatchInlineSnapshot(`1`) - expect(a.inclusive).toMatchInlineSnapshot(`false`) - } - { - const a = Number.range(-1, -3, { inclusive: true }) - expect(a.start).toMatchInlineSnapshot(`-1`) - expect(a.end).toMatchInlineSnapshot(`-3`) - expect(a.step).toMatchInlineSnapshot(`-1`) - expect(a.inclusive).toMatchInlineSnapshot(`true`) - } - { - const a = Number.range(-1, -3, { step: 4, inclusive: function () {} }) - expect(a.start).toMatchInlineSnapshot(`-1`) - expect(a.end).toMatchInlineSnapshot(`-3`) - expect(a.step).toMatchInlineSnapshot(`4`) - expect(a.inclusive).toMatchInlineSnapshot(`true`) - } - { - const a = Number.range(0, 5) - expect(() => Object.getOwnPropertyDescriptor(a, "start").call({})).toThrow() - } + expect(that(Iterator.range(0, 0, { step: NaN }))).toMatchInlineSnapshot(`""`) + expect(that(Iterator.range(0, 5, NaN))).toMatchInlineSnapshot(`""`) }) test("Step infer", () => { - expect(that(Number.range(0, -2))).toMatchInlineSnapshot(`"0f, -1f"`) - expect(that(BigInt.range(0n, -2n))).toMatchInlineSnapshot(`"0n, -1n"`) - expect(that(Number.range(0, -2, { inclusive: true }))).toMatchInlineSnapshot(`"0f, -1f, -2f"`) - expect(that(BigInt.range(0n, -2n, { inclusive: true }))).toMatchInlineSnapshot(`"0n, -1n, -2n"`) + expect(that(Iterator.range(0, -2))).toMatchInlineSnapshot(`"0f, -1f"`) + expect(that(Iterator.range(0n, -2n))).toMatchInlineSnapshot(`"0n, -1n"`) + expect(that(Iterator.range(0, -2, { inclusive: true }))).toMatchInlineSnapshot(`"0f, -1f, -2f"`) + expect(that(Iterator.range(0n, -2n, { inclusive: true }))).toMatchInlineSnapshot(`"0n, -1n, -2n"`) }) test("Error handling: Type Mismatch", () => { @@ -123,43 +95,37 @@ test("Error handling: Type Mismatch", () => { [0n, 1n, { step: 1 }], ] for (const each of sharedMatrix) { - expect(() => Number.range(...each)).toThrowError() - expect(() => BigInt.range(...each)).toThrowError() + expect(() => Iterator.range(...each)).toThrowError() + expect(() => Iterator.range(...each)).toThrowError() } - expect(() => Number.range(0n, 1n)).toThrowError() - expect(() => Number.range(0n, 1n, 1n)).toThrowError() - expect(() => Number.range(0n, 1n, { step: 1n })).toThrowError() - expect(() => BigInt.range(0, 1)).toThrowError() - expect(() => BigInt.range(0n, NaN)).toThrowError() - expect(() => BigInt.range(0, 1, 1)).toThrowError() - expect(() => BigInt.range(0, 1, { step: 1 })).toThrowError() + expect(() => Iterator.range(0n, NaN)).toThrowError() }) test("Error: Zero as step", () => { - expect(() => Number.range(0, 10, 0)).toThrowError() - expect(() => Number.range(0, 10, { step: 0 })).toThrowError() - expect(() => BigInt.range(0n, 10n, 0n)).toThrowError() - expect(() => BigInt.range(0n, 10n, { step: 0n })).toThrowError() + expect(() => Iterator.range(0, 10, 0)).toThrowError() + expect(() => Iterator.range(0, 10, { step: 0 })).toThrowError() + expect(() => Iterator.range(0n, 10n, 0n)).toThrowError() + expect(() => Iterator.range(0n, 10n, { step: 0n })).toThrowError() }) test("Error: Infinity as start / step", () => { - expect(() => Number.range(Infinity, 10, 0)).toThrowError() - expect(() => Number.range(-Infinity, 10, 0)).toThrowError() - expect(() => Number.range(0, 10, Infinity)).toThrowError() - expect(() => Number.range(0, 10, { step: Infinity })).toThrowError() + expect(() => Iterator.range(Infinity, 10, 0)).toThrowError() + expect(() => Iterator.range(-Infinity, 10, 0)).toThrowError() + expect(() => Iterator.range(0, 10, Infinity)).toThrowError() + expect(() => Iterator.range(0, 10, { step: Infinity })).toThrowError() }) test("Incompatible receiver", () => { function* x() {} const y = x() - const z = Number.range(0, 8) + const z = Iterator.range(0, 8) expect(() => z.next.call(y)).toThrow() expect(() => y.next.call(z)).toThrow() y.next() }) test("Inclusive on same start-end (issue #38)", () => { - expect(that(Number.range(0, 0, { inclusive: true }))).toMatchInlineSnapshot(`"0f"`) + expect(that(Iterator.range(0, 0, { inclusive: true }))).toMatchInlineSnapshot(`"0f"`) }) function that(x) { diff --git a/build-polyfill.mjs b/build-polyfill.mjs new file mode 100644 index 0000000..083448b --- /dev/null +++ b/build-polyfill.mjs @@ -0,0 +1,11 @@ +import builder from "core-js-builder" + +const bundle = await builder({ + modules: [/esnext.+iterator/], + summary: { + console: { size: true, modules: false }, + comment: { size: false, modules: true }, + }, + format: "bundle", + filename: './iterator-helper.js', +}) diff --git a/compare.md b/compare.md index c6859bf..daa05f4 100644 --- a/compare.md +++ b/compare.md @@ -8,16 +8,16 @@ Based on the document and REPL of other languages, might have error in it. ### Syntax -| Language | Syntax | -| ------------------ | ------------------------------------------------------------------------ | -| This proposal | `Number.range(start, to, step?)`
`Bigint.range(start, to, step?)` | -| Python | `range(start, to, step?)`
`range(to)` | -| Java | `IntStream.range(start, to)`
`LongStream.range(start, to)` | -| Swift (`Range`) | `start...to`
`start.. `(start..=to)` | -| Haskell | `[start,next_element_to_infer_step..to]` | -| F# | `seq { start .. step .. to }` | +| Language | Syntax | +| ------------------ | -------------------------------------------------------------------------- | +| This proposal | `Iterator.range(start, to, step?)`
`Bigint.range(start, to, step?)` | +| Python | `range(start, to, step?)`
`range(to)` | +| Java | `IntStream.range(start, to)`
`LongStream.range(start, to)` | +| Swift (`Range`) | `start...to`
`start.. `(start..=to)` | +| Haskell | `[start,next_element_to_infer_step..to]` | +| F# | `seq { start .. step .. to }` | Haskell: The `[start..to]` syntax produce a list. Due to the lazy evaluation of Haskell, it range semantics is different than most of languages. @@ -58,7 +58,7 @@ Define: - This proposal: It doesn't have it own class currently but it have it's own prototype `%RangeIteratorPrototype%` and have unique getters on it. - Java: The base interface of `IntStream` (`Stream`) doesn't implements `Iterator` protocol but have a `iterator()` methods that returns an Iterator. Must use with `for(int i: range.iterator())` - Swift (`StrideTo`): According to the [document of `StrideTo`](https://developer.apple.com/documentation/swift/strideto/1689269-lazy), laziness is opt-in. -- Rust: See https://github.com/tc39/proposal-Number.range/issues/17#issuecomment-642064127 +- Rust: See - Haskell: No Iterator / Iterable. The laziness is in the language. `The idea of a side-effecting iterator is antithetical to the Haskell Way.` (start StackOverflow) ### Immutable (`start`, `to` and `step` cannot be changed) diff --git a/global.d.ts b/global.d.ts index c23ed03..c852fc3 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,21 +1,11 @@ type Infinity = number -interface RangeIterator - extends Iterator { - // This property is not in the spec yet. - [Symbol.iterator](): RangeIterator - readonly [Symbol.toStringTag]: "RangeIterator" - readonly start: T - readonly end: T | Infinity - readonly step: T - readonly inclusive: boolean +declare var Iterator: { + new (): Iterator + prototype: Iterator + range(start: number, end: number, option: number | NumericRangeOptions): IterableIterator + range(start: bigint, end: bigint | Infinity, option: bigint | NumericRangeOptions): IterableIterator } -type RangeFunction = { - range( - start: T, - end: T | Infinity, - option?: T | { step?: T; inclusive?: boolean } - ): RangeIterator - range(start: T, end: T | Infinity, step?: T): RangeIterator +interface NumericRangeOptions { + step?: number | number + inclusive?: boolean } -interface NumberConstructor extends RangeFunction {} -interface BigIntConstructor extends RangeFunction {} diff --git a/index.html b/index.html index aeaf43f..79df8e4 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ -Proposal Number.range & BigInt.range + + + Open your console and Iterator.range is available. + + + - + function display(f) { + const stringify = f + .toString() + .replace(/^\(\) =>/, "") + .replace(/^\s+/gm, "") + .split("\n") + .map((x, i) => (i > 0 ? " " + x : x)) + .join("\n") + console.log(stringify, stringify.includes("\n") ? "\n" : "", f()) + } + display(() => Iterator.range(-2, 8).toArray()) + display(() => Iterator.range(-2n, 8n).toArray()) + display(() => Iterator.range(2, -2, -1).toArray()) + display(() => Iterator.range(0, 5, { step: 1.5 }).toArray()) + display(() => + Iterator.range(1, 10) + .take(4) + .map((x) => x ** x) + .toArray() + ) + + + diff --git a/polyfill.js b/polyfill.js index 7f6567f..da43a03 100644 --- a/polyfill.js +++ b/polyfill.js @@ -11,17 +11,16 @@ It should only be used to collect developers feedback about the APIs.`) } const generatorPrototype = Object.getPrototypeOf(Object.getPrototypeOf((function* () {})())) const origNext = generatorPrototype.next - generatorPrototype.next = new Proxy(origNext, { - apply(target, thisArg, args) { - let isRange = false - try { - Object.getOwnPropertyDescriptor(NumericRangeIterator.prototype, "start").get.call(thisArg) - isRange = true - } catch {} - if (isRange) throw new TypeError() - return Reflect.apply(target, thisArg, args) - }, - }) + /** @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 @@ -32,7 +31,7 @@ It should only be used to collect developers feedback about the APIs.`) * @param {T} zero * @param {T} one */ - function* closure(start, end, step, inclusiveEnd, zero, one) { + function* NumericRangeIteratorObject(start, end, step, inclusiveEnd, zero, one) { if (isNaN(start)) return if (isNaN(end)) return if (isNaN(step)) return @@ -41,11 +40,9 @@ It should only be used to collect developers feedback about the APIs.`) if (ifIncrease !== ifStepIncrease) return let hitsEnd = false let currentCount = zero - while (hitsEnd === false) { - // @ts-ignore + while (hitsEnd === false) { // @ts-ignore let currentYieldingValue = start + step * currentCount - if (currentYieldingValue === end) hitsEnd = true - // @ts-ignore + if (currentYieldingValue === end) hitsEnd = true // @ts-ignore currentCount = currentCount + one let endCondition = false if (ifIncrease) { @@ -61,77 +58,71 @@ It should only be used to collect developers feedback about the APIs.`) return undefined } /** - * @param {Parameters} args + * @param {Parameters} args */ function CreateNumericRangeIteratorWithInternalSlot(...args) { - const g = closure(...args) + const g = NumericRangeIteratorObject(...args) Reflect.setPrototypeOf(g, new.target.prototype) return g } /** * @template {number | bigint} T - */ - // @ts-ignore + */ // @ts-ignore class NumericRangeIterator extends CreateNumericRangeIteratorWithInternalSlot { /** * @param {T} start * @param {T | number | undefined} end * @param {T | undefined | null | { step?: T, inclusive?: boolean }} option * @param {(typeof SpecValue)[keyof typeof SpecValue]} type - */ - // @ts-ignore + */ // @ts-ignore constructor(start, end, option, type) { - const primitiveType = type === SpecValue.NumberRange ? "number" : "bigint" - if (typeof start !== primitiveType) throw new TypeError() // @ts-ignore - /** @type {T} */ const zero = type === SpecValue.BigIntRange ? 0n : 0 // @ts-ignore - /** @type {T} */ const one = type === SpecValue.BigIntRange ? 1n : 1 - // Allowing all kinds of number (number, bigint, decimals, ...) to range from a finite number to infinity. - if (!isInfinity(end) && typeof end !== primitiveType) throw new TypeError() + /** @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() const ifIncrease = end > start let inclusiveEnd = false + /** @type {T} */ let step if (option === undefined || option === null) step = undefined else if (typeof option === "object") { step = option.step inclusiveEnd = Boolean(option.inclusive) - } else if (typeof option === primitiveType) step = option + } // + else if (type === SpecValue.NumberRange && typeof option === "number") step = option + else if (type === SpecValue.BigIntRange && typeof option === "bigint") step = option else throw new TypeError() if (step === undefined || step === null) { - if (ifIncrease) step = one - // @ts-ignore + if (ifIncrease) step = one // @ts-ignore else step = -one } - if (typeof step !== primitiveType) throw new TypeError() + + 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) - this.#start = start - this.#end = end - this.#step = step - this.#inclusive = inclusiveEnd - // @ts-ignore + const obj = super(start, end, step, inclusiveEnd, zero, one) // @ts-ignore return obj } + #brandCheck next() { - this.#start // brand check + this.#brandCheck return origNext.call(this) } - #start - #end - #step - #inclusive - get start() { - return this.#start - } - get end() { - return this.#end - } - get step() { - return this.#step - } - get inclusive() { - return this.#inclusive + static { + isRangeIterator = (o) => #brandCheck in o } } const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) @@ -142,15 +133,14 @@ It should only be used to collect developers feedback about the APIs.`) configurable: true, value: "NumericRangeIterator", }) - Object.defineProperty(Number, "range", { + Object.defineProperty(Iterator, "range", { configurable: true, writable: true, - value: (start, end, option) => new NumericRangeIterator(start, end, option, SpecValue.NumberRange), - }) - Object.defineProperty(BigInt, "range", { - configurable: true, - writable: true, - value: (start, end, option) => new NumericRangeIterator(start, end, option, SpecValue.BigIntRange), + value: (start, end, option) => { + if (typeof start === "number") return new NumericRangeIterator(start, end, option, SpecValue.NumberRange) + if (typeof start === "bigint") return new NumericRangeIterator(start, end, option, SpecValue.BigIntRange) + throw new TypeError("Iterator.range only supports number and bigint.") + }, }) function isInfinity(x) { if (typeof x !== "number") return false diff --git a/spec.emu b/spec.emu index f63a4ce..824db47 100644 --- a/spec.emu +++ b/spec.emu @@ -12,162 +12,120 @@ copyright: false contributors: Jack Works - -

Number

- -

Number.range ( _start_, _end_, _option_ )

- - 1. Return ? CreateNumericRangeIterator(_start_, _end_, _option_, ~number-range~). - -
-
- -

BigInt

- -

BigInt.range ( _start_, _end_, _option_ )

- - 1. Return ? CreateNumericRangeIterator(_start_, _end_, _option_, ~bigint-range~). - -
-
- -

Algorithms

- -

CreateNumericRangeIterator ( _start_, _end_, _option_, _type_ )

-
- - 1. If _type_ is ~number-range~, let _primitiveType_ be *"number"*, else let _primitiveType_ be *"bigint"*. - 1. If Type(_start_) is not _primitiveType_, throw a TypeError. - 1. If _type_ is ~bigint-range~, let _zero_ be *0n*, else let _zero_ be *0*. - 1. If _type_ is ~bigint-range~, let _one_ be *1n*, else let _one_ be *1*. - 1. NOTE: Allowing all kinds of number (number, bigint, decimals, ...) to range from a finite number to infinity.. - 1. If _end_ is not *+∞* or *-∞* and if Type(_end_) is not _primitiveType_, throw a *TypeError* exception. - 1. If _start_ is *+∞* or *-∞*, throw a *RangeError* exception. - 1. Let _ifIncrease_ be end > start. - 1. Let _inclusiveEnd_ be *false*. - 1. If _option_ is *undefined* or *null*, let _step_ be *undefined*. - 1. Else if Type(_option_) is *object*, then - 1. Let _step_ be ? Get(_option_, "step"). - 1. Let _inclusiveEnd_ be ToBoolean(? Get(_option_, "inclusive")). - 1. Else if Type(_option_) is _primitiveType_, let _step_ be _option_. - 1. Else, throw a *TypeError* exception. - 1. If _step_ is *undefined* or *null*, then - 1. If _ifIncrease_ is *true*, let _step_ be _one_. - 1. Else let _step_ be -_one_. - 1. If Type(_step_) is not _primitiveType_, throw a *TypeError* exception. - 1. If _step_ is *+∞* or *-∞*, throw a *RangeError* exception. - 1. If _step_ is _zero_ and _start_ is not equal to _end_, throw a *RangeError* exception. - 1. Let _closure_ be a new Abstract Closure with no parameters that captures _start_, _end_, _step_, _inclusiveEnd_, _zero_, _one_ and performs the following steps when called: - 1. If _start_ is *NaN*, return *undefined*. - 1. If _end_ is *NaN*, return *undefined*. - 1. If _step_ is *NaN*, return *undefined*. - 1. Let _ifIncrease_ be _end_ > _start_. - 1. Let _ifStepIncrease_ be _step_ > _zero_. - 1. If _ifIncrease_ is not equal to _ifStepIncrease_, return *undefined*. - 1. Let _hitsEnd_ be *false*. - 1. Let _currentCount_ be _zero_. - 1. NOTE: Debug these steps at https://tc39.es/proposal-Number.range/playground.html . - 1. Repeat, while _hitsEnd_ is *false*, - 1. Let _currentYieldingValue_ be _start_ + (_step_ \* _currentCount_). - 1. If _currentYieldingValue_ equal to _end_, Set _hitsEnd_ to *true*. - 1. Set _currentCount_ to _currentCount_ + _one_. - NOTE: Prevent value overflow. - 1. Let _endCondition_ be *false*. - 1. If _ifIncrease_ is *true*, then - 1. If _inclusiveEnd_ is *true*, set _endCondition_ be _currentYieldingValue_ > _end_. - 1. Else set _endCondition_ be _currentYieldingValue_ >= _end_. - 1. Else, - 1. If _inclusiveEnd_ is *true*, set _endCondition_ be _end_ > _currentYieldingValue_. - 1. Else set _endCondition_ be _end_ >= _currentYieldingValue_. - 1. If _endCondition_ is *true*, return *undefined*. - 1. Perform ? Yield(_currentYieldingValue_). - 1. Return *undefined*. - 1. Let _iterator_ be CreateIteratorFromClosure(_closure_, ~Numberic Range Iterator~, %NumericRangeIteratorPrototype%, « start, end, step, inclusiveEnd »). - 1. Set _iterator_.[[start]] to _start_. - 1. Set _iterator_.[[end]] to _end_. - 1. Set _iterator_.[[step]] to _step_. - 1. Set _iterator_.[[inclusiveEnd]] to _inclusiveEnd_. - 1. Return _iterator_. - -
-
+ +

Control Abstraction Objects

- -

Properties of the NumericRangeIterator Prototype Object

-

The NumericRangeIterator prototype object is the intrinsic object %NumericRangeIteratorPrototype%.

-

The value has a [[Prototype]] internal slot whose value is the intrinsic object %IteratorPrototype%.

- -

%NumericRangeIterator%.next ( )

- - 1. Return ? GeneratorResume(*this* value, *undefined*, ~Numeric Range Iterator~). - -
- -

%NumericRangeIteratorPrototype%.[@@toStringTag]

-

The initial value of the @@toStringTag property is the String value `"NumericRangeIterator"`.

-

This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.

-
- -

%NumericRangeIteratorPrototype%.start

-

%NumericRangeIteratorPrototype%.start is an accessor property with attributes { [[Enumerable]]: false, [[Configurable]]: *true*}. The [[Get]] attribute is defined as follows:

- -

get %NumericRangeIteratorPrototype%.start

- - 1. Perform ? RequireInternalSlot(*this*, [[start]]). - 1. Return *this*.[[start]]. - -
-
- -

%NumericRangeIteratorPrototype%.end

-

%NumericRangeIteratorPrototype%.end is an accessor property with attributes { [[Enumerable]]: false, [[Configurable]]: *true*}. The [[Get]] attribute is defined as follows:

- -

get %NumericRangeIteratorPrototype%.end

- - 1. Perform ? RequireInternalSlot(*this*, [[end]]). - 1. Return *this*.[[end]]. - -
-
- -

%NumericRangeIteratorPrototype%.inclusive

-

%NumericRangeIteratorPrototype%.inclusive is an accessor property with attributes { [[Enumerable]]: false, [[Configurable]]: *true*}. The [[Get]] attribute is defined as follows:

- -

get %NumericRangeIteratorPrototype%.isEnclusiveEnd

- - 1. Perform ? RequireInternalSlot(*this*, [[inclusiveEnd]]). - 1. Return *this*.[[inclusiveEnd]]. - + +

Iteration

+ + +

Properties of the Iterator Constructor

+ + +

Iterator.range ( _start_, _end_, _option_ )

+ + 1. If _start_ is a Number, return ? CreateNumericRangeIterator(_start_, _end_, _option_, ~number-range~). + 1. If _start_ is a BigInt, return ? CreateNumericRangeIterator(_start_, _end_, _option_, ~bigint-range~). + 1. Throw a *TypeError* exception. + +
-
- -

%NumericRangeIteratorPrototype%.step

-

%NumericRangeIteratorPrototype%.step is an accessor property with attributes { [[Enumerable]]: false, [[Configurable]]: *true*}. The [[Get]] attribute is defined as follows:

- -

get %NumericRangeIteratorPrototype%.step

- - 1. Perform ? RequireInternalSlot(*this*, [[step]]). - 1. Return *this*.[[step]]. - + + +

The NumericRangeIterator Object

+

A NumericRangeIterator object is an iterator that yields numbers. There is not a named constructor for NumericRangeIterator objects. Instead, NumericRangeIterator objects are created by the CreateNumericRangeIterator abstract operation as needed.

+ + +

+ CreateNumericRangeIterator ( + _start_: a Number or a BigInt, + _end_: an ECMAScript language value, + _option_: an ECMAScript language value, + _type_: ~number-range~ or ~bigint-range~ + ) +

+
+ + 1. If _type_ is ~number-range~, then + 1. Assert: _start_ is a Number. + 1. If _end_ is not a Number, throw a *TypeError* exception. + 1. Let _zero_ be *0*. + 1. Let _one_ be *1*. + 1. Else, + 1. Assert: _start_ is a BigInt. + Allowing all kinds of number (bigint, decimals, ...) to range from a finite number to infinity. + 1. If _end_ is not *+∞* or *-∞* and _end_ is not a BigInt, throw a *TypeError* exception. + 1. Let _zero_ be *0n*. + 1. Let _one_ be *1n*. + 1. If _start_ is *+∞* or *-∞*, throw a *RangeError* exception. + 1. Let _ifIncrease_ be end > start. + 1. Let _inclusiveEnd_ be *false*. + 1. If _option_ is *undefined* or *null*, let _step_ be *undefined*. + 1. Else if _option_ is an Object, then + 1. Let _step_ be ? Get(_option_, "step"). + 1. Let _inclusiveEnd_ be ToBoolean(? Get(_option_, "inclusive")). + 1. Else if _type_ is ~number-range~ and _option_ is a Number, let _step_ be _option_. + 1. Else if _type_ is ~bigint-range~ and _option_ is a BigInt, let _step_ be _option_. + 1. Else, throw a *TypeError* exception. + 1. If _step_ is *undefined* or *null*, then + 1. If _ifIncrease_ is *true*, let _step_ be _one_. + 1. Else let _step_ be -_one_. + 1. If _type_ is ~number-range~ and _step_ is not a Number, throw a *TypeError* exception. + 1. Else if _type_ is ~bigint-range~ and _step_ is not a BigInt, throw a *TypeError* exception. + 1. If _step_ is *+∞* or *-∞*, throw a *RangeError* exception. + 1. If _step_ is _zero_ and _start_ is not equal to _end_, throw a *RangeError* exception. + 1. Let _closure_ be a new Abstract Closure with no parameters that captures _start_, _end_, _step_, _inclusiveEnd_, _zero_, _one_ and performs the following steps when called: + 1. If _start_ is *NaN*, return *undefined*. + 1. If _end_ is *NaN*, return *undefined*. + 1. If _step_ is *NaN*, return *undefined*. + 1. Let _ifIncrease_ be _end_ > _start_. + 1. Let _ifStepIncrease_ be _step_ > _zero_. + 1. If _ifIncrease_ is not equal to _ifStepIncrease_, return *undefined*. + 1. Let _hitsEnd_ be *false*. + 1. Let _currentCount_ be _zero_. + 1. NOTE: You can debug these steps at https://tc39.es/proposal-Number.range/playground.html . + 1. Repeat, while _hitsEnd_ is *false*, + 1. Let _currentYieldingValue_ be _start_ + (_step_ \* _currentCount_). + 1. If _currentYieldingValue_ equal to _end_, Set _hitsEnd_ to *true*. + 1. Set _currentCount_ to _currentCount_ + _one_. + NOTE: Prevent value overflow. + 1. Let _endCondition_ be *false*. + 1. If _ifIncrease_ is *true*, then + 1. If _inclusiveEnd_ is *true*, set _endCondition_ be _currentYieldingValue_ > _end_. + 1. Else set _endCondition_ be _currentYieldingValue_ >= _end_. + 1. Else, + 1. If _inclusiveEnd_ is *true*, set _endCondition_ be _end_ > _currentYieldingValue_. + 1. Else set _endCondition_ be _end_ >= _currentYieldingValue_. + 1. If _endCondition_ is *true*, return *undefined*. + 1. Perform ? Yield(_currentYieldingValue_). + 1. Return *undefined*. + 1. Let _iterator_ be CreateIteratorFromClosure(_closure_, *"%NumericRangeIteratorPrototype%"*, %NumericRangeIteratorPrototype%). + 1. Return _iterator_. + +
+ + +

The %NumericRangeIteratorPrototype% Object

+

The %NumericRangeIteratorPrototype% object:

+
    +
  • has properties that are inherited by all NumericRangeIterator Objects.
  • +
  • is an ordinary object.
  • +
  • has a [[Prototype]] internal slot whose value is %IteratorPrototype%.
  • +
  • has the following properties:
  • +
+ + +

%NumericRangeIterator%.next ( )

+ + 1. Return ? GeneratorResume(*this* value, ~empty~, *"%NumericRangeIteratorPrototype%"*). + +
+ +

%NumericRangeIteratorPrototype%.[@@toStringTag]

+

The initial value of the @@toStringTag property is the String value `"NumericRangeIterator"`.

+

This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.

+
+
- -

Internal slots

- - - - - - - - - - - - - - -
Internal slotDescription
[[start]]The range starts start.
[[end]]The range ends to.
[[step]]The range step size.
[[inclusiveEnd]]Boolean. If the end is inclusive.
-
-