From 4aa2a51e2f21c2727f94ce57056a94646a9a9449 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Sat, 4 Apr 2020 13:42:02 +0800 Subject: [PATCH] feat: allow range to inf on bigint, close #8 --- index.html | 11 ++- polyfill.js | 190 ++++++++++++++++++++++++++++++++++------------------ spec.emu | 28 ++++---- 3 files changed, 141 insertions(+), 88 deletions(-) diff --git a/index.html b/index.html index e7fa90b..65614c5 100644 --- a/index.html +++ b/index.html @@ -1845,11 +1845,8 @@

3.1 BigInt.range(from, to,

4 Algorithms

4.1 CreateRangeIterator(from, to, step, type)

-
  1. If Type(from) is not type, throw a TypeError exception.
  2. Assert: type is "number" or - "bigint"
  3. If type is "bigint", let zero be 0n, else let zero be 0.
  4. If type is "bigint", let one be 1n, else let one be 1. - 1.
    1. If variant isAcceptAlias is false, do nothing.
    2. Else, if Type(to) is undefined, let to be from, then from be zero
  5. If Type(step) is undefined, let step = one -
  6. If Type(from) is not type, throw a TypeError exception.
  7. If Type(to) is not type, throw a TypeError exception.
  8. If Type(step) is not type, throw a TypeError exception. -
  9. If from is Infinity, throws a RangeError exception.
  10. If step is Infinity, throws a RangeError exception.
  11. If step is zero, throws an RangeError exception.
  12. Let iterator be ObjectCreate(%RangeIteratorPrototype%, « [[from]], [[to]], [[step]], [[type]], [[currentCount]], [[lastValue]] »).
  13. Set iterator.[[from]] to from.
  14. Set iterator.[[to]] to to.
  15. Set iterator.[[step]] to step.
  16. Set iterator.[[type]] to type.
  17. Set iterator.[[currentCount]] to one.
  18. Set iterator.[[lastValue]] to from.
  19. Return iterator. +
    1. If Type(from) is not type, throw a TypeError exception.
    2. Assert: type is "number" or + "bigint"
    3. If type is "bigint", let zero be 0n, else let zero be 0.
    4. If type is "bigint", let one be 1n, else let one be 1.
    5. Variants
      1. If variant isAcceptAlias is false, do nothing.
      2. Else, if Type(to) is "undefined", let to be from, then from be zero
    6. If Type(step) is undefined, let step = one
    7. If Type(from) is not type, throw a TypeError exception.
    8. Note: Editor's Note
      Allowing all kinds of number (number, bigint, decimals, ...) to range from a finite number to infinity.
    9. If to is not +∞ or -∞ and if Type(to) is not type, throw a TypeError exception.
    10. If Type(step) is not type, throw a TypeError exception.
    11. If from is +∞ or -∞, throws a RangeError exception.
    12. If step is +∞ or -∞, throws a RangeError exception.
    13. If step is zero, throws an RangeError exception.
    14. Let iterator be ObjectCreate(%RangeIteratorPrototype%, « [[from]], [[to]], [[step]], [[type]], [[currentCount]], [[lastValue]] »).
    15. Set iterator.[[from]] to from.
    16. Set iterator.[[to]] to to.
    17. Set iterator.[[step]] to step.
    18. Set iterator.[[type]] to type.
    19. Set iterator.[[currentCount]] to one.
    20. Set iterator.[[lastValue]] to from.
    21. Return iterator.
    @@ -1860,7 +1857,7 @@

    5 Properties of the RangeIterator Prototype Obje

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

    5.1 %RangeIterator%.next()

    -
    1. Let iterator be the this value.
    2. If Type(iterator) is not Object, throw a TypeError exception.
    3. If iterator does not have all of the internal slots of a Range Iterator Instance, throw a TypeError exception. +
      1. Let iterator be the this value.
      2. If Type(iterator) is not Object, throw a TypeError exception.
      3. If iterator does not have all of the internal slots of a Range Iterator Instance, throw a TypeError exception.
      4. Let from be iterator.[[from]].
      5. Let to be iterator.[[to]].
      6. Let step be iterator.[[step]].
      7. Let type be iterator.[[type]].
      8. Assert: type is "number" or "bigint"
      9. If type is "bigint", let zero be 0n, else let zero be 0
      10. If type is "bigint", let one be 1n, else let one be 1
      11. If from is NaN, return CreateIterResultObject(undefined, true).
      12. If to is NaN, return CreateIterResultObject(undefined, true).
      13. If step is NaN, return CreateIterResultObject(undefined, true).
      14. Let ifIncrease be to > from
      15. Let ifStepIncrease to be step > zero
      16. Variants
        1. If variant directionMismatch is "throw"
          1. If ifIncrease is not equal to ifStepIncrease, throw a RangeError exception.
        2. If variant directionMismatch is "yield-no-value"
          1. If ifIncrease is not equal to ifStepIncrease, return CreateIterResultObject(undefined, true).
        3. If variant directionMismatch is "ignore"
          1. If ifIncrease is true, let step to be abs(step)
          2. Else let step to be -abs(step)
        4. If variant directionMismatch is "noop"
          1. Do nothing @@ -1877,7 +1874,7 @@

            5.1 %RangeIterator%.next()

            currentCount++ yield yielding } -
          2. Let currentCount be iterator.[[currentCount]]
          3. Let lastValue be iterator.[[lastValue]]
          4. Let now be from
          5. If ifIncrease is true, let condition be !(lastValue >= to), else let condition be !(to >= lastValue)
          6. Repeat, while condition evaluates to true,
            1. Let yielding be lastValue
            2. Set lastValue be from + (step * currentCount)
            3. Set currentCount to currentCount + one
            4. Set iterator.[[currentCount]] to currentCount
            5. Return CreateIterResultObject(yielding, false). +
            6. Let currentCount be iterator.[[currentCount]]
            7. Let lastValue be iterator.[[lastValue]]
            8. If ifIncrease is true, let condition be !(lastValue >= to), else let condition be !(to >= lastValue)
            9. Repeat, while condition evaluates to true,
              1. Set lastValue be from + (step * currentCount)
              2. Set currentCount to currentCount + one
              3. Set iterator.[[currentCount]] to currentCount
              4. Set iterator.[[lastValue]] to lastValue
              5. Return CreateIterResultObject(lastValue, false).
            10. return CreateIterResultObject(undefined, true).
            diff --git a/polyfill.js b/polyfill.js index cf562e0..98092c6 100644 --- a/polyfill.js +++ b/polyfill.js @@ -1,7 +1,9 @@ /// -// syntax target = es6 -// This polyfill requires: globalThis +// syntax target = es2020 +// This polyfill requires: globalThis, BigInt & private fields ;(() => { + // Math.abs does not support BigInt. + const abs = (x) => (x >= (typeof x === 'bigint' ? 0n : 0) ? x : -x) /* * Behaviour flags * This proposal is in early stage. @@ -23,90 +25,146 @@ * yield-no-value: return undefined, yield nothing */ const directionMismatch = 'ignore' - /** Polyfill start */ - const FakeBigIntConstructor = x => x - /** @type {BigIntConstructor} */ - const BigInt = globalThis.BigInt || FakeBigIntConstructor - /** - * - * @param {number | bigint} from - * @param {number | bigint} to - * @param {number | bigint | undefined} step - * @param {"number" | "bigint"} type - */ - function* CreateRangeIterator(from, to, step, type) { - if (typeof from !== type) throw new TypeError() - if (type !== 'number' && type !== 'bigint') throw new TypeError() - const zero = type === 'number' ? 0 : BigInt(0) - const one = type === 'number' ? 1 : BigInt(1) - if (isAcceptAlias) { - if (typeof to === 'undefined') { + const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) + class RangeIterator { + /** + * + * @param {number | bigint} from + * @param {number | bigint | undefined} to + * @param {number | bigint | undefined} step + * @param {"number" | "bigint"} type + */ + constructor(from, to, step, type) { + // Step 1 to 7 + if (typeof from !== type) throw new TypeError() + if (type !== 'number' && type !== 'bigint') throw new TypeError('Assert failed') + const zero = type === 'bigint' ? 0n : 0 + const one = type === 'bigint' ? 1n : 1 + if (isAcceptAlias === false) { + } else if (typeof to === 'undefined') { + // range(to) equals to range(zero, to) to = from from = zero } - } else { + if (typeof step === 'undefined') step = one + if (typeof from !== type) throw new TypeError() + // Step 9 to 13 + // Allowing all kinds of number (number, bigint, decimals, ...) to range from a finite number to infinity. + if (typeof to === 'number' && Number.isFinite(to)) if (typeof to !== type) throw new TypeError() + if (typeof step !== type) throw new TypeError() + // JavaScript is awesome, this code won't work + // if (!Number.isFinite(from) || !Number.isFinite(step)) throw RangeError() + if ( + (typeof from === 'number' && !Number.isFinite(from)) || + (typeof step === 'number' && !Number.isFinite(step)) + ) + throw RangeError() + if (step === zero) throw new RangeError() + // Step 14 + Object.setPrototypeOf(RangeIterator.prototype, IteratorPrototype) + // Step 15 - 21 + this.#from = from + this.#to = to + this.#step = step + this.#type = type + this.#currentCount = one + this.#lastValue = from + return this } - if (typeof step === 'undefined') step = one - if (typeof from !== type || typeof to !== type || typeof step !== type) throw new TypeError() - if ( - (typeof from === 'number' && !Number.isFinite(from)) || - (typeof step === 'number' && !Number.isFinite(step)) - ) - throw new RangeError() - if (step === zero) throw new RangeError() - if (Number.isNaN(from) || Number.isNaN(to) || Number.isNaN(step)) return undefined - // 13. let ifIncrease = to > from - const ifIncrease = to > from - const ifStepIncrease = step > zero - switch (directionMismatch) { - case 'throw': + //#region internal slots + /** @type {number | bigint} */ + #from + /** @type {number | bigint} */ + #to + /** @type {number | bigint} */ + #step + /** @type {"number" | "bigint"} */ + #type + /** @type {number | bigint} */ + #currentCount + /** @type {number | bigint} */ + #lastValue + //#endregion + /** + * @returns {IteratorResult} + */ + next() { + // Step 1 to 3 omitted. Private field will do the brand check + const from = this.#from + const to = this.#to + let step = this.#step + const type = this.#type + if (type !== 'bigint' && type !== 'number') throw new TypeError('Assertion failed') + const zero = type === 'bigint' ? 0n : 0 + const one = type === 'bigint' ? 1n : 1 + if (Number.isNaN(from) || Number.isNaN(to) || Number.isNaN(step)) return { done: true, value: undefined } + const ifIncrease = to > from + const ifStepIncrease = step > zero + if (directionMismatch === 'throw') { if (ifIncrease !== ifStepIncrease) throw new RangeError() - break - case 'yield-no-value': - return undefined - case 'ignore': - // Math.abs does not support BigInt. - const abs = x => (x >= (typeof x === 'bigint' ? BigInt(0) : 0) ? x : -x) - if (ifIncrease) step = abs(step) + } else if (directionMismatch === 'yield-no-value') { + if (ifIncrease !== ifStepIncrease) return { done: true, value: undefined } + } else if (directionMismatch === 'ignore') { + if (ifIncrease === true) step = abs(step) else step = -abs(step) - break - case 'noop': - break - default: - throw new Error('Bad variant directionMismatch') - } - let currentCount = one - let lastValue = from - if (ifIncrease) { - while (!(lastValue >= to)) { - let yielding = lastValue - lastValue = from + step * currentCount - currentCount++ - yield yielding + } else if (directionMismatch === 'noop') { + // 16.d.i: Do nothing + } else { + throw new TypeError('Invalid directionMismatch') } - } else { - while (!(to >= lastValue)) { - let yielding = lastValue + // Step 18 + /* +let currentCount = one +let lastValue = from +let condition = ifIncrease ? "!(lastValue >= to)" : "!(to >= lastValue)" +while (eval(condition)) { + let yielding = lastValue + lastValue = from + step * currentCount + currentCount++ + yield yielding +} + */ + let currentCount = this.#currentCount + let lastValue = this.#lastValue + const condition = ifIncrease ? () => !(lastValue >= to) : () => !(to >= lastValue) + while (condition()) { lastValue = from + step * currentCount - currentCount++ - yield yielding + currentCount = currentCount + one + this.#currentCount = currentCount + this.#lastValue = lastValue + return { done: false, value: lastValue } } + return { done: true, undefined } + } + get from() { + return this.#from + } + get to() { + return this.#to + } + get step() { + return this.#step } - return undefined } + Object.defineProperty(RangeIterator.prototype, Symbol.toStringTag, { + writable: false, + enumerable: false, + configurable: true, + value: 'RangeIterator', + }) if (typeof Number.range !== 'function') { Object.defineProperty(Number, 'range', { configurable: true, - value: (from, to, step) => CreateRangeIterator(from, to, step, 'number'), - writable: true + value: (from, to, step) => new RangeIterator(from, to, step, 'number'), + writable: true, }) } // If BigInt does not exist in globalThis, this will apply to FakeBigIntConstructor and then ignored. if (typeof BigInt.range !== 'function') { Object.defineProperty(BigInt, 'range', { configurable: true, - value: (from, to, step) => CreateRangeIterator(from, to, step, 'bigint'), - writable: true + value: (from, to, step) => new RangeIterator(from, to, step, 'bigint'), + writable: true, }) } })() diff --git a/spec.emu b/spec.emu index b13f028..91e54dd 100644 --- a/spec.emu +++ b/spec.emu @@ -42,22 +42,21 @@ contributors: "Jack Works"

            CreateRangeIterator(_from_, _to_, _step_, _type_)

            - 1. If Type(from) is not _type_, throw a *TypeError* exception. + 1. If Type(_from_) is not _type_, throw a *TypeError* exception. 1. Assert: _type_ is *"number"* or *"bigint"* 1. If _type_ is *"bigint"*, let _zero_ be *0n*, else let _zero_ be *0*. 1. If _type_ is *"bigint"*, let _one_ be *1n*, else let _one_ be *1*. - 1. + 1. Variants 1. If variant _isAcceptAlias_ is *false*, do nothing. - 1. Else, if Type(to) is undefined, let _to_ be _from_, then _from_ be _zero_ - 1. If Type(step) is *undefined*, let _step_ = _one_ - - 1. If Type(from) is not _type_, throw a *TypeError* exception. - 1. If Type(to) is not _type_, throw a *TypeError* exception. - 1. If Type(step) is not _type_, throw a *TypeError* exception. - - 1. If _from_ is *Infinity*, throws a *RangeError* exception. - 1. If _step_ is *Infinity*, throws a *RangeError* exception. + 1. Else, if Type(_to_) is *"undefined"*, let _to_ be _from_, then _from_ be _zero_ + 1. If Type(_step_) is *undefined*, let _step_ = _one_ + 1. If Type(_from_) is not _type_, throw a *TypeError* exception. + 1. Note: Allowing all kinds of number (number, bigint, decimals, ...) to range from a finite number to infinity. + 1. If _to_ is not *+∞* or *-∞* and if Type(_to_) is not _type_, throw a *TypeError* exception. + 1. If Type(_step_) is not _type_, throw a *TypeError* exception. + 1. If _from_ is *+∞* or *-∞*, throws a *RangeError* exception. + 1. If _step_ is *+∞* or *-∞*, throws a *RangeError* exception. 1. If _step_ is _zero_, throws an *RangeError* exception. 1. Let _iterator_ be ObjectCreate(%RangeIteratorPrototype%, « [[from]], [[to]], [[step]], [[type]], [[currentCount]], [[lastValue]] »). 1. Set _iterator_.[[from]] to from. @@ -79,7 +78,7 @@ contributors: "Jack Works"

            %RangeIterator%.next()

            1. Let _iterator_ be the *this* value. - 1. If Type(iterator) is not Object, throw a *TypeError* exception. + 1. If Type(_iterator_) is not Object, throw a *TypeError* exception. 1. If _iterator_ does not have all of the internal slots of a Range Iterator Instance, throw a *TypeError* exception. 1. Let _from_ be _iterator_.[[from]]. @@ -123,14 +122,13 @@ while (eval(condition)) { 1. Let _currentCount_ be _iterator_.[[currentCount]] 1. Let _lastValue_ be _iterator_.[[lastValue]] - 1. Let _now_ be _from_ 1. If _ifIncrease_ is true, let _condition_ be `!(lastValue >= to)`, else let _condition_ be `!(to >= lastValue)` 1. Repeat, while _condition_ evaluates to *true*, - 1. Let _yielding_ be _lastValue_ 1. Set _lastValue_ be _from_ + (_step_ \* _currentCount_) 1. Set _currentCount_ to _currentCount_ + _one_ 1. Set _iterator_.[[currentCount]] to currentCount - 1. Return CreateIterResultObject(_yielding_, *false*). + 1. Set _iterator_.[[lastValue]] to lastValue + 1. Return CreateIterResultObject(_lastValue_, *false*). 1. return CreateIterResultObject(*undefined*, *true*).