From baf3e86b16f5c8f6c896b686a5be8693a68b25d3 Mon Sep 17 00:00:00 2001 From: Jack Works Date: Sun, 7 Apr 2024 13:04:56 +0800 Subject: [PATCH] resolve reviews from Michael Ficarra --- index.html | 891 ++++++++++++++++++++++++++++++++++++++++++--------- package.json | 17 +- polyfill.js | 45 +-- spec.emu | 65 ++-- 4 files changed, 814 insertions(+), 204 deletions(-) diff --git a/index.html b/index.html index f7165ea..d57ab63 100644 --- a/index.html +++ b/index.html @@ -82,7 +82,7 @@ if (e.code === 'Escape') { sdoBox.deactivate(); } - }) + }), ); }); @@ -99,11 +99,11 @@ this.$searchBox.addEventListener( 'keydown', - debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true }) + debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true }), ); this.$searchBox.addEventListener( 'keyup', - debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true }) + debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true }), ); // Perform an initial search if the box is not empty. @@ -303,7 +303,6 @@ } if (text) { - // prettier-ignore html += ``; } }); @@ -355,6 +354,14 @@ this._pinnedIds = {}; this.loadPinEntries(); + // unpin all button + document + .querySelector('#menu-pins .unpin-all') + .addEventListener('click', this.unpinAll.bind(this)); + + // individual unpinning buttons + this.$pinList.addEventListener('click', this.pinListClick.bind(this)); + // toggle menu this.$toggle.addEventListener('click', this.toggle.bind(this)); @@ -404,8 +411,8 @@ e.stopPropagation(); if (e.keyCode === 80) { this.togglePinEntry(); - } else if (e.keyCode > 48 && e.keyCode < 58) { - this.selectPin(e.keyCode - 49); + } else if (e.keyCode >= 48 && e.keyCode < 58) { + this.selectPin((e.keyCode - 9) % 10); } }; @@ -455,25 +462,68 @@ }; function findActiveClause(root, path) { - let clauses = getChildClauses(root); path = path || []; - for (let $clause of clauses) { - let rect = $clause.getBoundingClientRect(); + let visibleClauses = getVisibleClauses(root, path); + let midpoint = Math.floor(window.innerHeight / 2); + + for (let [$clause, path] of visibleClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); + let isFullyVisibleAboveTheFold = + clauseTop > 0 && clauseTop < midpoint && clauseBottom < window.innerHeight; + if (isFullyVisibleAboveTheFold) { + return path; + } + } + + visibleClauses.sort(([, pathA], [, pathB]) => pathB.length - pathA.length); + for (let [$clause, path] of visibleClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); let $header = $clause.querySelector('h1'); + let clauseStyles = getComputedStyle($clause); let marginTop = Math.max( - parseInt(getComputedStyle($clause)['margin-top']), - parseInt(getComputedStyle($header)['margin-top']) + 0, + parseInt(clauseStyles['margin-top']), + parseInt(getComputedStyle($header)['margin-top']), ); - - if (rect.top - marginTop <= 1 && rect.bottom > 0) { - return findActiveClause($clause, path.concat($clause)) || path; + let marginBottom = Math.max(0, parseInt(clauseStyles['margin-bottom'])); + let crossesMidpoint = + clauseTop - marginTop <= midpoint && clauseBottom + marginBottom >= midpoint; + if (crossesMidpoint) { + return path; } } return path; } +function getVisibleClauses(root, path) { + let childClauses = getChildClauses(root); + path = path || []; + + let result = []; + + let seenVisibleClause = false; + for (let $clause of childClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); + let isPartiallyVisible = + (clauseTop > 0 && clauseTop < window.innerHeight) || + (clauseBottom > 0 && clauseBottom < window.innerHeight) || + (clauseTop < 0 && clauseBottom > window.innerHeight); + + if (isPartiallyVisible) { + seenVisibleClause = true; + let innerPath = path.concat($clause); + result.push([$clause, innerPath]); + result.push(...getVisibleClauses($clause, innerPath)); + } else if (seenVisibleClause) { + break; + } + } + + return result; +} + function* getChildClauses(root) { for (let el of root.children) { switch (el.nodeName) { @@ -524,6 +574,7 @@ return; } + let text; if (entry.type === 'clause') { let prefix; if (entry.number) { @@ -531,12 +582,14 @@ } else { prefix = ''; } - // prettier-ignore - this.$pinList.innerHTML += `
  • ${prefix}${entry.titleHTML}
  • `; + text = `${prefix}${entry.titleHTML}`; } else { - this.$pinList.innerHTML += `
  • ${getKey(entry)}
  • `; + text = getKey(entry); } + let link = `${text}`; + this.$pinList.innerHTML += `
  • ${link}
  • `; + if (Object.keys(this._pinnedIds).length === 0) { this.showPins(); } @@ -545,7 +598,7 @@ }; Menu.prototype.removePinEntry = function (id) { - let item = this.$pinList.querySelector(`a[href="${makeLinkToId(id)}"]`).parentNode; + let item = this.$pinList.querySelector(`li[data-section-id="${id}"]`); this.$pinList.removeChild(item); delete this._pinnedIds[id]; if (Object.keys(this._pinnedIds).length === 0) { @@ -555,6 +608,21 @@ this.persistPinEntries(); }; +Menu.prototype.unpinAll = function () { + for (let id of Object.keys(this._pinnedIds)) { + this.removePinEntry(id); + } +}; + +Menu.prototype.pinListClick = function (event) { + if (event?.target?.classList.contains('unpin')) { + let id = event.target.parentNode.dataset.sectionId; + if (id) { + this.removePinEntry(id); + } + } +}; + Menu.prototype.persistPinEntries = function () { try { if (!window.localStorage) return; @@ -768,6 +836,10 @@ this.$header.appendChild(this.$headerText); this.$headerRefId = document.createElement('a'); this.$header.appendChild(this.$headerRefId); + this.$header.addEventListener('pointerdown', e => { + this.dragStart(e); + }); + this.$closeButton = document.createElement('span'); this.$closeButton.setAttribute('id', 'references-pane-close'); this.$closeButton.addEventListener('click', () => { @@ -776,18 +848,20 @@ this.$header.appendChild(this.$closeButton); this.$pane.appendChild(this.$header); - let tableContainer = document.createElement('div'); - tableContainer.setAttribute('id', 'references-pane-table-container'); + this.$tableContainer = document.createElement('div'); + this.$tableContainer.setAttribute('id', 'references-pane-table-container'); this.$table = document.createElement('table'); this.$table.setAttribute('id', 'references-pane-table'); this.$tableBody = this.$table.createTBody(); - tableContainer.appendChild(this.$table); - this.$pane.appendChild(tableContainer); + this.$tableContainer.appendChild(this.$table); + this.$pane.appendChild(this.$tableContainer); - menu.$specContainer.appendChild(this.$container); + if (menu != null) { + menu.$specContainer.appendChild(this.$container); + } }, activate() { @@ -807,7 +881,7 @@ let previousId; let previousCell; let dupCount = 0; - this.$headerRefId.textContent = '#' + entry.id; + this.$headerRefId.innerHTML = getKey(entry); this.$headerRefId.setAttribute('href', makeLinkToId(entry.id)); this.$headerRefId.style.display = 'inline'; (entry.referencingIds || []) @@ -838,6 +912,7 @@ this.$table.removeChild(this.$tableBody); this.$tableBody = newBody; this.$table.appendChild(this.$tableBody); + this.autoSize(); }, showSDOs(sdos, alternativeId) { @@ -855,7 +930,6 @@ e.parentNode.replaceChild(document.createTextNode(e.textContent), e); }); - // prettier-ignore this.$headerText.innerHTML = `Syntax-Directed Operations for
    ${parentName} ${colons.outerHTML} `; this.$headerText.querySelector('a').append(rhs); this.showSDOsBody(sdos, alternativeId); @@ -884,6 +958,34 @@ this.$table.removeChild(this.$tableBody); this.$tableBody = newBody; this.$table.appendChild(this.$tableBody); + this.autoSize(); + }, + + autoSize() { + this.$tableContainer.style.height = + Math.min(250, this.$table.getBoundingClientRect().height) + 'px'; + }, + + dragStart(pointerDownEvent) { + let startingMousePos = pointerDownEvent.clientY; + let startingHeight = this.$tableContainer.getBoundingClientRect().height; + let moveListener = pointerMoveEvent => { + if (pointerMoveEvent.buttons === 0) { + removeListeners(); + return; + } + let desiredHeight = startingHeight - (pointerMoveEvent.clientY - startingMousePos); + this.$tableContainer.style.height = Math.max(0, desiredHeight) + 'px'; + }; + let listenerOptions = { capture: true, passive: true }; + let removeListeners = () => { + document.removeEventListener('pointermove', moveListener, listenerOptions); + this.$header.removeEventListener('pointerup', removeListeners, listenerOptions); + this.$header.removeEventListener('pointercancel', removeListeners, listenerOptions); + }; + document.addEventListener('pointermove', moveListener, listenerOptions); + this.$header.addEventListener('pointerup', removeListeners, listenerOptions); + this.$header.addEventListener('pointercancel', removeListeners, listenerOptions); }, }; @@ -914,7 +1016,9 @@ referencePane.showReferencesFor(this.entry); }); this.$container.appendChild(this.$permalink); + this.$container.appendChild(document.createTextNode(' ')); this.$container.appendChild(this.$pinLink); + this.$container.appendChild(document.createTextNode(' ')); this.$container.appendChild(this.$refsLink); document.body.appendChild(this.$outer); }, @@ -1092,13 +1196,16 @@ } function init() { + if (document.getElementById('menu') == null) { + return; + } menu = new Menu(); let $container = document.getElementById('spec-container'); $container.addEventListener( 'mouseover', debounce(e => { Toolbox.activateIfMouseOver(e); - }) + }), ); document.addEventListener( 'keydown', @@ -1109,7 +1216,7 @@ } document.getElementById('shortcuts-help').classList.remove('active'); } - }) + }), ); } @@ -1165,12 +1272,40 @@ return [...menu.$menu.querySelectorAll('.active')].map(getTocPath).filter(p => p != null); } -function loadStateFromSessionStorage() { - if (!window.sessionStorage || typeof menu === 'undefined' || window.navigating) { +function initTOCExpansion(visibleItemLimit) { + // Initialize to a reasonable amount of TOC expansion: + // * Expand any full-breadth nesting level up to visibleItemLimit. + // * Expand any *single-item* level while under visibleItemLimit (even if that pushes over it). + + // Limit to initialization by bailing out if any parent item is already expanded. + const tocItems = Array.from(document.querySelectorAll('#menu-toc li')); + if (tocItems.some(li => li.classList.contains('active') && li.querySelector('li'))) { + return; + } + + const selfAndSiblings = maybe => Array.from(maybe?.parentNode.children ?? []); + let currentLevelItems = selfAndSiblings(tocItems[0]); + let availableCount = visibleItemLimit - currentLevelItems.length; + while (availableCount > 0 && currentLevelItems.length) { + const nextLevelItems = currentLevelItems.flatMap(li => selfAndSiblings(li.querySelector('li'))); + availableCount -= nextLevelItems.length; + if (availableCount > 0 || currentLevelItems.length === 1) { + // Expand parent items of the next level down (i.e., current-level items with children). + for (const ol of new Set(nextLevelItems.map(li => li.parentNode))) { + ol.closest('li').classList.add('active'); + } + } + currentLevelItems = nextLevelItems; + } +} + +function initState() { + if (typeof menu === 'undefined' || window.navigating) { return; } - if (sessionStorage.referencePaneState != null) { - let state = JSON.parse(sessionStorage.referencePaneState); + const storage = typeof sessionStorage !== 'undefined' ? sessionStorage : Object.create(null); + if (storage.referencePaneState != null) { + let state = JSON.parse(storage.referencePaneState); if (state != null) { if (state.type === 'ref') { let entry = menu.search.biblio.byId[state.id]; @@ -1184,39 +1319,36 @@ referencePane.showSDOsBody(sdos, state.id); } } - delete sessionStorage.referencePaneState; + delete storage.referencePaneState; } } - if (sessionStorage.activeTocPaths != null) { - document - .getElementById('menu-toc') - .querySelectorAll('.active') - .forEach(e => { - e.classList.remove('active'); - }); - let active = JSON.parse(sessionStorage.activeTocPaths); + if (storage.activeTocPaths != null) { + document.querySelectorAll('#menu-toc li.active').forEach(li => li.classList.remove('active')); + let active = JSON.parse(storage.activeTocPaths); active.forEach(activateTocPath); - delete sessionStorage.activeTocPaths; + delete storage.activeTocPaths; + } else { + initTOCExpansion(20); } - if (sessionStorage.searchValue != null) { - let value = JSON.parse(sessionStorage.searchValue); + if (storage.searchValue != null) { + let value = JSON.parse(storage.searchValue); menu.search.$searchBox.value = value; menu.search.search(value); - delete sessionStorage.searchValue; + delete storage.searchValue; } - if (sessionStorage.tocScroll != null) { - let tocScroll = JSON.parse(sessionStorage.tocScroll); + if (storage.tocScroll != null) { + let tocScroll = JSON.parse(storage.tocScroll); menu.$toc.scrollTop = tocScroll; - delete sessionStorage.tocScroll; + delete storage.tocScroll; } } -document.addEventListener('DOMContentLoaded', loadStateFromSessionStorage); +document.addEventListener('DOMContentLoaded', initState); -window.addEventListener('pageshow', loadStateFromSessionStorage); +window.addEventListener('pageshow', initState); window.addEventListener('beforeunload', () => { if (!window.sessionStorage || typeof menu === 'undefined') { @@ -1239,7 +1371,7 @@ // https://w3c.github.io/csswg-drafts/css-counter-styles/ const lowerLetters = Array.from({ length: 26 }, (_, i) => - String.fromCharCode('a'.charCodeAt(0) + i) + String.fromCharCode('a'.charCodeAt(0) + i), ); // Implement the lower-alpha 'alphabetic' algorithm, // adjusting for indexing from 0 rather than 1. @@ -1310,7 +1442,7 @@ function addStepNumberText( ol, depth = 0, - special = [...ol.classList].some(c => c.startsWith('nested-')) + special = [...ol.classList].some(c => c.startsWith('nested-')), ) { let counter = !special && counterByDepth[depth]; if (!counter) { @@ -1335,7 +1467,12 @@ const marker = document.createElement('span'); marker.textContent = `${i < cache.length ? cache[i] : getTextForIndex(i)}. `; marker.setAttribute('aria-hidden', 'true'); - li.prepend(marker); + const attributesContainer = li.querySelector('.attributes-tag'); + if (attributesContainer == null) { + li.prepend(marker); + } else { + attributesContainer.insertAdjacentElement('afterend', marker); + } for (const sublist of li.querySelectorAll(':scope > ol')) { addStepNumberText(sublist, depth + 1, special); } @@ -1349,14 +1486,185 @@ }); }); +'use strict'; + +// Update superscripts to not suffer misinterpretation when copied and pasted as plain text. +// For example, +// * Replace `103` with +// `103` +// so it gets pasted as `10**3` rather than `103`. +// * Replace `10-x` with +// `10-x` +// so it gets pasted as `10**-x` rather than `10-x`. +// * Replace `2a + 1` with +// `2**(a + 1)` +// so it gets pasted as `2**(a + 1)` rather than `2a + 1`. + +function makeExponentPlainTextSafe(sup) { + // Change a only if it appears to be an exponent: + // * text-only and contains only mathematical content (not e.g. `1st`) + // * contains only s and internal links (e.g. + // `2(_y_)`) + const isText = [...sup.childNodes].every(node => node.nodeType === 3); + const text = sup.textContent; + if (isText) { + if (!/^[0-9. 𝔽ℝℤ()=*×/÷±+\u2212-]+$/u.test(text)) { + return; + } + } else { + if (sup.querySelector('*:not(var, emu-xref, :scope emu-xref a)')) { + return; + } + } + + let prefix = '**'; + let suffix = ''; + + // Add wrapping parentheses unless they are already present + // or this is a simple (possibly signed) integer or single-variable exponent. + const skipParens = + /^[±+\u2212-]?(?:[0-9]+|\p{ID_Start}\p{ID_Continue}*)$/u.test(text) || + // Split on parentheses and remember them; the resulting parts must + // start and end empty (i.e., with open/close parentheses) + // and increase depth to 1 only at the first parenthesis + // to e.g. wrap `(a+1)*(b+1)` but not `((a+1)*(b+1))`. + text + .trim() + .split(/([()])/g) + .reduce((depth, s, i, parts) => { + if (s === '(') { + return depth > 0 || i === 1 ? depth + 1 : NaN; + } else if (s === ')') { + return depth > 0 ? depth - 1 : NaN; + } else if (s === '' || (i > 0 && i < parts.length - 1)) { + return depth; + } + return NaN; + }, 0) === 0; + if (!skipParens) { + prefix += '('; + suffix += ')'; + } + + sup.insertAdjacentHTML('beforebegin', ``); + if (suffix) { + sup.insertAdjacentHTML('afterend', ``); + } +} + +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('sup:not(.text)').forEach(sup => { + makeExponentPlainTextSafe(sup); + }); +}); + let sdoMap = JSON.parse(`{}`); -let biblio = JSON.parse(`{"refsByClause":{"sec-iterator.range":["_ref_0","_ref_1"],"sec-numeric-range-iterator-object":["_ref_2"],"sec-create-numeric-range-iterator":["_ref_3"],"sec-%numericrangeiteratorprototype%-object":["_ref_4"]},"entries":[{"type":"clause","id":"sec-iterator.range","title":"Iterator.range ( start, end, option )","titleHTML":"Iterator.range ( start, end, option )","number":"27.1.2.1"},{"type":"clause","id":"sec-properties-of-the-iterator-constructor","titleHTML":"Properties of the Iterator Constructor","number":"27.1.2"},{"type":"term","term":"NumericRangeIterator","refId":"sec-numeric-range-iterator-object"},{"type":"op","aoid":"CreateNumericRangeIterator","refId":"sec-create-numeric-range-iterator"},{"type":"clause","id":"sec-create-numeric-range-iterator","title":"CreateNumericRangeIterator ( start, end, option, type )","titleHTML":"CreateNumericRangeIterator ( start, end, option, type )","number":"27.1.3.1","referencingIds":["_ref_0","_ref_1","_ref_2"]},{"type":"term","term":"%NumericRangeIteratorPrototype%","refId":"sec-%numericrangeiteratorprototype%-object"},{"type":"clause","id":"sec-properties-of-the-numericrangeiterator-prototype-object-next","titleHTML":"%NumericRangeIterator%.next ( )","number":"27.1.3.2.1"},{"type":"clause","id":"sec-properties-of-the-numericrangeiterator-prototype-object-@@tostringtag","titleHTML":"%NumericRangeIteratorPrototype%.[@@toStringTag]","number":"27.1.3.2.2"},{"type":"clause","id":"sec-%numericrangeiteratorprototype%-object","titleHTML":"The %NumericRangeIteratorPrototype% Object","number":"27.1.3.2","referencingIds":["_ref_3"]},{"type":"clause","id":"sec-numeric-range-iterator-object","title":"The NumericRangeIterator Object","titleHTML":"The NumericRangeIterator Object","number":"27.1.3","referencingIds":["_ref_4"]},{"type":"clause","id":"sec-iteration","titleHTML":"Iteration","number":"27.1"},{"type":"clause","id":"sec-control-abstraction-objects","titleHTML":"Control Abstraction Objects","number":"27"}]}`); -;let usesMultipage = false
    +
    • Toggle shortcuts help?
    • Toggle "can call user code" annotationsu
    • Jump to search box/
    • -

    Stage 1 Draft / April 6, 2023

    Range proposal

    +

    Stage 1 Draft / April 7, 2024

    Range proposal

    27 Control Abstraction Objects

    @@ -2635,26 +3233,25 @@

    27.1 Iteration

    27.1.2 Properties of the Iterator Constructor

    -

    27.1.2.1 Iterator.range ( start, end, option )

    -
    1. If start is a Number, return ? CreateNumericRangeIterator(start, end, option, number-range).
    2. If start is a BigInt, return ? CreateNumericRangeIterator(start, end, option, bigint-range).
    3. Throw a TypeError exception.
    +

    27.1.2.1 Iterator.range ( start, end, optionOrStep )

    +
    1. If start is a Number, return ? CreateNumericRangeIterator(start, end, optionOrStep, number-range).
    2. If start is a BigInt, return ? CreateNumericRangeIterator(start, end, optionOrStep, bigint-range).
    3. Throw a TypeError exception.
    -

    27.1.3 The NumericRangeIterator Object

    +

    27.1.3 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.

    -

    27.1.3.1 CreateNumericRangeIterator ( start, end, option, type )

    -

    The abstract operation CreateNumericRangeIterator takes arguments start (a Number or a BigInt), end (an ECMAScript language value), option (an ECMAScript language value), and type (number-range or bigint-range). It performs the following steps when called:

    +

    27.1.3.1 CreateNumericRangeIterator ( start, end, optionOrStep, type )

    +

    The abstract operation CreateNumericRangeIterator takes arguments start (a Number or a BigInt), end (an ECMAScript language value), optionOrStep (an ECMAScript language value), and type (number-range or bigint-range) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:

    1. If start is NaN, throw a RangeError exception.
    2. If end is NaN, throw a RangeError exception.
    3. If type is number-range, then
      1. Assert: start is a Number.
      2. If end is not a Number, throw a TypeError exception.
      3. Let zero be 0.
      4. Let one be 1.
    4. Else,
      1. Assert: start is a BigInt. - Editor's Note
        Allowing all kinds of number (bigint, decimals, ...) to range from a finite number to infinity.
      2. If end is not +∞𝔽 or -∞𝔽 and end is not a BigInt, throw a TypeError exception.
      3. Let zero be 0n.
      4. Let one be 1n.
    5. If start is +∞𝔽 or -∞𝔽, throw a RangeError exception.
    6. Let ifIncrease be end > start.
    7. Let inclusiveEnd be false.
    8. If option is undefined or null, let step be undefined.
    9. Else if option is an Object, then
      1. Let step be ? Get(option, "step").
      2. Let inclusiveEnd be ToBoolean(? Get(option, "inclusive")).
    10. Else if type is number-range and option is a Number, let step be option.
    11. Else if type is bigint-range and option is a BigInt, let step be option.
    12. Else, throw a TypeError exception.
    13. If step is undefined or null, then
      1. If ifIncrease is true, let step be one.
      2. Else let step be -one.
    14. If step is NaN, throw a RangeError exception.
    15. If type is number-range and step is not a Number, throw a TypeError exception.
    16. Else if type is bigint-range and step is not a BigInt, throw a TypeError exception.
    17. If step is +∞𝔽 or -∞𝔽, throw a RangeError exception.
    18. If step is zero and start is not equal to end, throw a RangeError exception.
    19. 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. Let ifIncrease be end > start.
      2. Let ifStepIncrease be step > zero.
      3. If ifIncrease is not equal to ifStepIncrease, return undefined.
      4. Let hitsEnd be false.
      5. Let currentCount be zero.
      6. NOTE: You can debug these steps at https://tc39.es/proposal-Number.range/playground.html .
      7. Repeat, while hitsEnd is false,
        1. Let currentYieldingValue be start + (step * currentCount).
        2. If currentYieldingValue equal to end, Set hitsEnd to true.
        3. Set currentCount to currentCount + one. - NOTE: Prevent value overflow.
        4. Let endCondition be false.
        5. If ifIncrease is true, then
          1. If inclusiveEnd is true, set endCondition be currentYieldingValue > end.
          2. Else set endCondition be currentYieldingValue >= end.
        6. Else,
          1. If inclusiveEnd is true, set endCondition be end > currentYieldingValue.
          2. Else set endCondition be end >= currentYieldingValue.
        7. If endCondition is true, return undefined.
        8. Perform ? Yield(currentYieldingValue).
      8. Return undefined.
    20. Let iterator be CreateIteratorFromClosure(closure, "%NumericRangeIteratorPrototype%", %NumericRangeIteratorPrototype%).
    21. Return iterator.
    + Editor's Note
    Allowing all kinds of number (bigint, decimals, ...) to range from a finite number to infinity.
  • If end is not +∞𝔽 or -∞𝔽 and end is not a BigInt, throw a TypeError exception.
  • Let zero be 0.
  • Let one be 1.
  • If start is +∞𝔽 or -∞𝔽, throw a RangeError exception.
  • Let inclusiveEnd be false.
  • If optionOrStep is undefined or null, then
    1. Let step be undefined.
  • Else if optionOrStep is an Object, then
    1. Let step be ? Get(optionOrStep, "step").
    2. Set inclusiveEnd to ToBoolean(? Get(optionOrStep, "inclusive")).
  • Else if type is number-range and optionOrStep is a Number, then
    1. Let step be optionOrStep.
  • Else if type is bigint-range and optionOrStep is a BigInt, then
    1. Let step be optionOrStep.
  • Else,
    1. Throw a TypeError exception.
  • If step is undefined or null, then
    1. If end > start, let step be one.
    2. Else let step be -one.
  • If step is NaN, throw a RangeError exception.
  • If type is number-range and step is not a Number, throw a TypeError exception.
  • Else if type is bigint-range and step is not a BigInt, throw a TypeError exception.
  • If step is +∞𝔽 or -∞𝔽, throw a RangeError exception.
  • If step is zero and start is not end, throw a RangeError exception.
  • 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 end > start, let ifIncrease be true.
    2. Else let ifIncrease be false.
    3. If step > zero, let ifStepIncrease be true.
    4. Else let ifStepIncrease be false.
    5. If ifIncrease is not ifStepIncrease, return undefined.
    6. Let hitsEnd be false.
    7. Let currentCount be zero.
    8. NOTE: You can debug these steps at https://tc39.es/proposal-Number.range/playground.html .
    9. Repeat, while hitsEnd is false,
      1. Let currentYieldingValue be start + (step * currentCount).
      2. If currentYieldingValue is end, Set hitsEnd to true.
      3. Set currentCount to currentCount + one.
      4. If ifIncrease is true, then
        1. If inclusiveEnd is true, then
          1. If currentYieldingValue > end, return undefined.
        2. Else,
          1. If currentYieldingValueend, return undefined.
      5. Else,
        1. If inclusiveEnd is true, then
          1. If end > currentYieldingValue, return undefined.
        2. Else,
          1. If endcurrentYieldingValue, return undefined.
      6. Perform ? Yield(currentYieldingValue).
    10. Return undefined.
  • Return CreateIteratorFromClosure(closure, "%NumericRangeIteratorPrototype%", %NumericRangeIteratorPrototype%).
  • 27.1.3.2 The %NumericRangeIteratorPrototype% Object

    -

    The %NumericRangeIteratorPrototype% object:

    +

    The %NumericRangeIteratorPrototype% object:

    • has properties that are inherited by all NumericRangeIterator Objects.
    • is an ordinary object.
    • diff --git a/package.json b/package.json index 8189fe8..6dc9f25 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,13 @@ }, "license": "MIT", "devDependencies": { - "@tc39/ecma262-biblio": "2.1.2481", - "@types/jest": "^29.2.3", - "core-js": "^3.26.1", - "core-js-builder": "^3.26.1", - "ecmarkup": "^16.0.0", - "jest": "^29.3.1", - "prettier": "^2.8.0" - } + "@tc39/ecma262-biblio": "2.1.2719", + "@types/jest": "^29.5.12", + "core-js": "^3.36.1", + "core-js-builder": "^3.36.1", + "ecmarkup": "^18.3.1", + "jest": "^29.7.0", + "prettier": "^3.2.5" + }, + "packageManager": "pnpm@8.15.6+sha256.01c01eeb990e379b31ef19c03e9d06a14afa5250b82e81303f88721c99ff2e6f" } diff --git a/polyfill.js b/polyfill.js index 32b7584..a6e20be 100644 --- a/polyfill.js +++ b/polyfill.js @@ -42,15 +42,20 @@ It should only be used to collect developers feedback about the APIs.`) let currentYieldingValue = start + step * currentCount if (currentYieldingValue === end) hitsEnd = true // @ts-ignore currentCount = currentCount + one - let endCondition = false + // ifIncrease && inclusiveEnd && currentYieldingValue > end if (ifIncrease) { - if (inclusiveEnd) endCondition = currentYieldingValue > end - else endCondition = currentYieldingValue >= end + if (inclusiveEnd) { + if (currentYieldingValue > end) return + } else { + if (currentYieldingValue >= end) return + } } else { - if (inclusiveEnd) endCondition = end > currentYieldingValue - else endCondition = end >= currentYieldingValue + if (inclusiveEnd) { + if (end > currentYieldingValue) return + } else { + if (end >= currentYieldingValue) return + } } - if (endCondition) return yield currentYieldingValue } return undefined @@ -70,10 +75,10 @@ It should only be used to collect developers feedback about the APIs.`) /** * @param {T} start * @param {T | number | undefined} end - * @param {T | undefined | null | { step?: T, inclusive?: boolean }} option + * @param {T | undefined | null | { step?: T, inclusive?: boolean }} optionOrStep * @param {(typeof SpecValue)[keyof typeof SpecValue]} type */ // @ts-ignore - constructor(start, end, option, type) { + constructor(start, end, optionOrStep, type) { if (isNaN(start) || isNaN(end)) throw new RangeError() /** @type {T} */ let zero /** @type {T} */ let one @@ -90,21 +95,21 @@ It should only be used to collect developers feedback about the APIs.`) 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) + 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 option === "number") step = option - else if (type === SpecValue.BigIntRange && typeof option === "bigint") step = option + 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 (ifIncrease) step = one // @ts-ignore + if (end > start) + step = one // @ts-ignore else step = -one } @@ -136,9 +141,11 @@ It should only be used to collect developers feedback about the APIs.`) Object.defineProperty(Iterator, "range", { configurable: true, writable: true, - 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) + 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.") }, }) diff --git a/spec.emu b/spec.emu index 237704f..d08b758 100644 --- a/spec.emu +++ b/spec.emu @@ -22,10 +22,10 @@ contributors: Jack Works

      Properties of the Iterator Constructor

      -

      Iterator.range ( _start_, _end_, _option_ )

      +

      Iterator.range ( _start_, _end_, _optionOrStep_ )

      - 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. If _start_ is a Number, return ? CreateNumericRangeIterator(_start_, _end_, _optionOrStep_, ~number-range~). + 1. If _start_ is a BigInt, return ? CreateNumericRangeIterator(_start_, _end_, _optionOrStep_, ~bigint-range~). 1. Throw a *TypeError* exception.
      @@ -40,9 +40,9 @@ contributors: Jack Works CreateNumericRangeIterator ( _start_: a Number or a BigInt, _end_: an ECMAScript language value, - _option_: an ECMAScript language value, + _optionOrStep_: an ECMAScript language value, _type_: ~number-range~ or ~bigint-range~ - ) + ) : either a normal completion containing an ECMAScript language value or a throw completion
      @@ -57,50 +57,55 @@ contributors: Jack Works 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. Let _zero_ be *0*. + 1. Let _one_ be *1*. 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 _optionOrStep_ is *undefined* or *null*, then + 1. Let _step_ be *undefined*. + 1. Else if _optionOrStep_ is an Object, then + 1. Let _step_ be ? Get(_optionOrStep_, *"step"*). + 1. Set _inclusiveEnd_ to ToBoolean(? Get(_optionOrStep_, *"inclusive"*)). + 1. Else if _type_ is ~number-range~ and _optionOrStep_ is a Number, then + 1. Let _step_ be _optionOrStep_. + 1. Else if _type_ is ~bigint-range~ and _optionOrStep_ is a BigInt, then + 1. Let _step_ be _optionOrStep_. + 1. Else, + 1. Throw a *TypeError* exception. 1. If _step_ is *undefined* or *null*, then - 1. If _ifIncrease_ is *true*, let _step_ be _one_. + 1. If _end_ > _start_, let _step_ be _one_. 1. Else let _step_ be -_one_. 1. If _step_ is *NaN*, throw a *RangeError* exception. 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. If _step_ is _zero_ and _start_ is not _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. Let _ifIncrease_ be _end_ > _start_. - 1. Let _ifStepIncrease_ be _step_ > _zero_. - 1. If _ifIncrease_ is not equal to _ifStepIncrease_, return *undefined*. + 1. If _end_ > _start_, let _ifIncrease_ be *true*. + 1. Else let _ifIncrease_ be *false*. + 1. If _step_ > _zero_, let _ifStepIncrease_ be *true*. + 1. Else let _ifStepIncrease_ be *false*. + 1. If _ifIncrease_ is not _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. If _currentYieldingValue_ is _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. If _inclusiveEnd_ is *true*, then + 1. If _currentYieldingValue_ > _end_, return *undefined*. + 1. Else, + 1. If _currentYieldingValue_ ≥ _end_, return *undefined*. 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. If _inclusiveEnd_ is *true*, then + 1. If _end_ > _currentYieldingValue_, return *undefined*. + 1. Else, + 1. If _end_ ≥ _currentYieldingValue_, return *undefined*. 1. Perform ? Yield(_currentYieldingValue_). 1. Return *undefined*. - 1. Let _iterator_ be CreateIteratorFromClosure(_closure_, *"%NumericRangeIteratorPrototype%"*, %NumericRangeIteratorPrototype%). - 1. Return _iterator_. + 1. Return CreateIteratorFromClosure(_closure_, *"%NumericRangeIteratorPrototype%"*, %NumericRangeIteratorPrototype%).