X-Git-Url: https://git.ladys.computer/Sutra/blobdiff_plain/86748e0641f8c4d833cf2cca4873925a6f6c64e8..refs/tags/0.1.0:/xsd/functions.js diff --git a/xsd/functions.js b/xsd/functions.js index 4ad5b5c..08acc8e 100644 --- a/xsd/functions.js +++ b/xsd/functions.js @@ -8,16 +8,33 @@ // file, You can obtain one at . import { + abs, bind, call, + get8BitUnsignedIntegralItem, + getByteLength, + getFirstSubstringIndex, getPrototype, isArrayBuffer, isFiniteNumber, isIntegralNumber, ITERATOR, + Matcher, + min, objectCreate, + rawString, sameValue, + set8BitIntegralItem, + stringCatenate, + stringIncludes, + stringPadEnd, + stringPadStart, + stringSlice, + stringSplit, + substring, + toExponentialNotation, toFloat32, + toNumber, type, } from "../deps.js"; import { div, mod } from "./operators.js"; @@ -438,3 +455,2736 @@ const { ), }; })(); + +/** + * Maps each digit to its numerical value. + * + * See . + * + * ☡ This function throws if the provided value is not a digit string. + * + * ※ This function is not exposed. + */ +const digitValue = (d) => +ensureMatches(lexicalDigit, d); + +/** + * Maps a sequence of digits to the position‐weighted sum of the terms + * numerical values. + * + * See . + * + * ☡ This function throws if the provided value is not an iterable of + * digit strings. + * + * ※ Although elsewhere the X·S·D specification claims that sequences + * count from zero, this sequence clearly must count from one. + * + * ※ This function is not exposed. + */ +const digitSequenceValue = (S) => { + let sum = 0; + for (const S_i of S) { + sum = sum * 10 + digitValue(S_i); + } + return sum; +}; + +/** + * Maps a sequence of digits to the position‐weighted sum of the terms + * numerical values, weighted appropriately for fractional digits. + * + * See . + * + * ☡ This function throws if the provided value is not an iterable of + * digit strings. + * + * ※ The X·S·D specification erroneously specifies ·digitValue·(Si) − + * 10^(−i), but ·digitValue·(Si) × 10^(−i) is correct. + * + * ※ Although elsewhere the X·S·D specification claims that sequences + * count from zero, this sequence clearly must count from one. + * + * ※ This function is not exposed. + */ +const fractionDigitSequenceValue = (S) => { + let sum = 0; + let i = 0; + for (const S_i of S) { + sum += digitValue(S_i) * 10 ** --i; + } + return sum; +}; + +/** + * Maps a fracFrag to the appropriate fractional decimal number. + * + * See . + * + * ☡ This function throws if the provided value is not a fracFrag + * string. + * + * ※ This function is not exposed. + */ +//deno-lint-ignore no-unused-vars +const fractionFragValue = (N) => + fractionDigitSequenceValue( + literalSequence(ensureMatches(fracFrag, N)), + ); + +/** + * Maps an unsignedNoDecimalPtNumeral to its numerical value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * unsignedNoDecimalPtNumeral string. + */ +export const unsignedNoDecimalMap = (N) => + digitSequenceValue( + literalSequence(ensureMatches(unsignedNoDecimalPtNumeral, N)), + ); + +/** + * Maps an noDecimalPtNumeral to its numerical value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * noDecimalPtNumeral string. + */ +export const noDecimalMap = (N) => { + switch (ensureMatches(noDecimalPtNumeral, N)[0]) { + case "-": + return -unsignedNoDecimalMap(substring(N, 1)) || 0; + case "+": + return unsignedNoDecimalMap(substring(N, 1)); + default: + return unsignedNoDecimalMap(N); + } +}; + +/** + * Maps an unsignedDecimalPtNumeral to its numerical value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * unsignedDecimalPtNumeral string. + * + * ※ This function makes use of the built·in Ecmascript float parsing + * to avoid rounding errors. + */ +export const unsignedDecimalPtMap = (D) => { + // const N = substring( + // ensureMatches(unsignedDecimalPtNumeral, D), + // 0, + // getFirstSubstringIndex(D, "."), + // ); + // const F = substring(D, N.length + 1); + // if (F === "") { + // return unsignedNoDecimalMap(N); + // } else if (N === "") { + // return fractionFragValue(F); + // } else { + // return unsignedNoDecimalMap(N) + fractionFragValue(F); + // } + return stringToFloat(ensureMatches(unsignedDecimalPtNumeral, D)); +}; + +/** + * Maps an decimalPtNumeral to its numerical value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * decimalPtNumeral string. + */ +export const decimalPtMap = (N) => { + switch (ensureMatches(decimalPtNumeral, N)[0]) { + case "-": + return -unsignedDecimalPtMap(substring(N, 1)) || 0; + case "+": + return unsignedDecimalPtMap(substring(N, 1)); + default: + return unsignedDecimalPtMap(N); + } +}; + +/** + * Maps a scientificNotationNumeral to its numerical value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * scientificNotationNumeral string. + * + * ※ The X·S·D specification erroneously specifies + * ·unsignedDecimalPtMap·(E), but ·noDecimalMap·(E) is correct. + * + * ※ This function makes use of the built·in Ecmascript float parsing + * to avoid rounding errors. + */ +export const scientificMap = (N) => { + // const C = substring( + // ensureMatches(scientificNotationNumeral, N), + // 0, + // (() => { + // let index = 0; + // for (const char of literalSequence(N)) { + // if (char === "e" || char === "E") { + // return index; + // } else { + // ++index; + // continue; + // } + // } + // })(), + // ); + // const E = substring(N, C.length + 1); + // return getFirstSubstringIndex(N, ".") !== -1 + // ? decimalPtMap(C) * 10 ** noDecimalMap(E) + // : noDecimalMap(C) * 10 ** noDecimalMap(E); + return stringToFloat(ensureMatches(scientificNotationNumeral, N)) || + 0; +}; + +/** + * Maps each integer between 0 and 9 to the corresponding digit. + * + * See . + * + * ☡ This function throws if the provided value is not an integral + * number between 0 and 9 inclusive. + * + * ※ This function is not exposed. + */ +const digit = (i) => { + if (!(isIntegralNumber(i) && i >= 0 && i <= 9)) { + throw new TypeError( + `सूत्र: Expected an integral number between 0 and 9 inclusive, but got: ${i}.`, + ); + } else { + return `${i}`; + } +}; + +/** + * Maps each nonnegative integer to a sequence of integers used by + * ·digitSeq· to ultimately create an unsignedNoDecimalPtNumeral. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + * + * ※ The sequence produced by this function is infinite. + * + * ※ This function is not exposed. + */ +const digitRemainderSeq = (i) => { + nonnegativeInteger(i); + return generatorSequence(function* () { + let s_j = i; + while (true) { + yield s_j; + s_j = div(s_j, 10); + } + }); +}; + +/** + * Maps each nonnegative integer to a sequence of integers used by + * ·unsignedNoDecimalPtCanonicalMap· to create an + * unsignedNoDecimalPtNumeral. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + * + * ※ The sequence produced by this function is infinite. + * + * ※ This function is not exposed. + */ +const digitSeq = (i) => { + nonnegativeInteger(i); + return generatorSequence(function* () { + for (const i_j of digitRemainderSeq(i)) { + yield mod(i_j, 10); + } + }); +}; + +/** + * Maps a sequence of nonnegative integers to the index before the + * first zero term. + * + * See . + * + * ☡ This function throws if the values of the provided sequence are + * not nonnegative integers. + * + * ※ The X·S·D specification erroneously describes this function as + * giving the index *of* the first zero term, but it is actually the + * index of the last nonzero term. + * + * ※ The X·S·D specification erroneously describes this function as + * taking a sequence of nonnegative integers, but it is called with + * ·FractionDigitRemainderSeq·, which is a list of nonnegative decimal + * numbers. + * + * ※ This function is not exposed. + */ +const lastSignificantDigit = (s) => { + let j = 0; + for (const s_j of s) { + if (nonnegativeDecimalNumber(s_j) === 0) { + return j === 0 ? 0 : j - 1; + } else { + ++j; + continue; + } + } +}; + +/** + * Maps each nonnegative decimal number less than 1 to a sequence of + * decimal numbers used by ·fractionDigitSeq· to ultimately create an + * unsignedNoDecimalPtNumeral. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number less than 1. + * + * ※ The X·S·D specification erroneously specifies s_0 = f − 10 and + * s_(j+1) = (s_j ·mod· 1) − 10, but s_0 = f × 10 and s_(j+1) = (s_j + * ·mod· 1) × 10 is correct. + * + * ※ The implementation of this function uses string operations + * because Ecmascript does not (currently) have a lossless decimal + * type. + * + * ※ The sequence produced by this function is infinite. + * + * ※ This function is not exposed. + */ +const FractionDigitRemainderSeq = (f) => { + if (!(isFiniteNumber(f) && f >= 0 && f < 1)) { + throw new TypeError( + `सूत्र: Expected a nonnegative finite number less than 1, but got: ${f}.`, + ); + } else { + return generatorSequence(function* () { + let s_j = f * 10; + while (true) { + yield s_j; + s_j = mod(s_j, 1) * 10; + } + }); + } +}; + +/** + * Maps each nonnegative decimal number less than 1 to a sequence of + * integers used by ·fractionDigitsCanonicalFragmentMap· to ultimately + * create an unsignedNoDecimalPtNumeral. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number less than 1. + * + * ※ The sequence produced by this function is infinite. + * + * ※ This function is not exposed. + */ +const fractionDigitSeq = (f) => { + if (!(isFiniteNumber(f) && f >= 0 && f < 1)) { + throw new TypeError( + `सूत्र: Expected a nonnegative finite number less than 1, but got: ${f}.`, + ); + } else { + return generatorSequence(function* () { + for (const s_j of FractionDigitRemainderSeq(f)) { + yield div(s_j, 1); + } + }); + } +}; + +/** + * Maps each nonnegative decimal number less than 1 to a ·literal· used + * by ·unsignedDecimalPtCanonicalMap· to create an + * unsignedDecimalPtNumeral. + * + * See . + * + * ☡ This function throws if the provided value is not nonnegative and + * less than 1. + * + * ※ This function is not exposed. + */ +//deno-lint-ignore no-unused-vars +const fractionDigitsCanonicalFragmentMap = (f) => + stringCatenate(...generatorSequence(function* () { + const l = lastSignificantDigit(FractionDigitRemainderSeq(f)); + let j = 0; + for (const f_j of fractionDigitSeq(f)) { + yield digit(f_j); + if (j === l) { + break; + } else { + ++j; + continue; + } + } + })); + +/** + * Maps a nonnegative integer to a unsignedNoDecimalPtNumeral, its + * ·canonical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + */ +export const unsignedNoDecimalPtCanonicalMap = (i) => { + nonnegativeInteger(i); + return stringCatenate(...generatorSequence(function* () { + const l = lastSignificantDigit(digitRemainderSeq(i)); + const digits = new Array(l + 1); + let j = 0; + for (const i_j of digitSeq(i)) { + digits[l - j] = digit(i_j); + if (j === l) { + break; + } else { + ++j; + continue; + } + } + for (j = 0; j <= l; ++j) { + yield digits[j]; + } + })); +}; + +/** + * Maps an integer to a noDecimalPtNumeral, its ·canonical + * representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer. + */ +export const noDecimalPtCanonicalMap = (i) => + integer(i) < 0 + ? stringCatenate("-", unsignedNoDecimalPtCanonicalMap(-i)) + : unsignedNoDecimalPtCanonicalMap(i); + +/** + * Maps a nonnegative decimal number to a unsignedDecimalPtNumeral, its + * ·canonical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number. + * + * ※ This function makes use of the built·in Ecmascript float + * serialization to avoid rounding errors. + */ +export const unsignedDecimalPtCanonicalMap = (n) => { + // nonnegativeDecimalNumber(n); + // return stringCatenate( + // unsignedNoDecimalPtCanonicalMap(div(n, 1)), + // ".", + // fractionDigitsCanonicalFragmentMap(mod(n, 1)), + // ); + const exp = toExponentialNotation(nonnegativeDecimalNumber(n)); + const eIndex = getFirstSubstringIndex(exp, "e"); + const exponent = +substring(exp, eIndex + 1); + const zeroPaddedMantissa = stringPadEnd( + stringPadStart( + exp[0], + -exponent + 1, + "0", + ) + (eIndex > 1 ? substring(exp, 2, eIndex) : ""), + exponent + 2, + "0", + ); + const decimalPoint = exponent < 0 ? 1 : exponent + 1; + return stringCatenate( + substring(zeroPaddedMantissa, 0, decimalPoint), + ".", + substring(zeroPaddedMantissa, decimalPoint), + ); +}; + +/** + * Maps a decimal number to a decimalPtNumeral, its ·canonical + * representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a finite number. + */ +export const decimalPtCanonicalMap = (n) => + decimalNumber(n) < 0 + ? stringCatenate("-", unsignedDecimalPtCanonicalMap(-n)) + : unsignedDecimalPtCanonicalMap(n); + +/** + * Maps a nonnegative decimal number to a + * unsignedScientificNotationNumeral, its ·canonical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number. + * + * ※ This function makes use of the built·in Ecmascript float + * serialization to avoid rounding errors. + */ +export const unsignedScientificCanonicalMap = (n) => { + // nonnegativeDecimalNumber(n); + // return stringCatenate( + // unsignedDecimalPtCanonicalMap(n / 10 ** div(log10(n), 1)), + // "E", + // noDecimalPtCanonicalMap(div(log10(n), 1)), + // ); + const exp = toExponentialNotation(nonnegativeDecimalNumber(n)); + const [mantissa, exponent] = stringSplit(exp, "e"); + return stringCatenate( + mantissa, + stringIncludes(mantissa, ".") ? "E" : ".0E", + exponent[0] === "+" ? substring(exponent, 1) : exponent, + ); +}; + +/** + * Maps a decimal number to a scientificNotationNumeral, its ·canonical + * representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a finite number. + */ +export const scientificCanonicalMap = (n) => + decimalNumber(n) < 0 + ? stringCatenate("-", unsignedScientificCanonicalMap(-n)) + : unsignedScientificCanonicalMap(n); + +/** + * Maps the ·lexical representations· of ·special values· used with + * some numerical datatypes to those ·special values·. + * + * See . + * + * ☡ This function throws if the provided value is not a + * numericalSpecialRep string. + */ +export const specialRepValue = (c) => { + switch (ensureMatches(numericalSpecialRep, c)) { + case "INF": + case "+INF": + return positiveInfinity; + case "-INF": + return negativeInfinity; + case "NaN": + return notANumber; + } +}; + +/** + * Maps the ·special values· used with some numerical datatypes to + * their ·canonical representations·. + * + * See . + * + * ☡ This function throws if the provided value is not nan or positive + * or negative infinity. + */ +export const specialRepCanonicalMap = (c) => { + if (sameValue(c, positiveInfinity)) { + return "INF"; + } else if (sameValue(c, negativeInfinity)) { + return "-INF"; + } else if (sameValue(c, notANumber)) { + return "NaN"; + } else { + throw new TypeError( + `सूत्र: Expected a special value, but got: ${c}.`, + ); + } +}; + +/** + * Maps a decimalLexicalRep onto a decimal value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * decimalLexicalRep string. + */ +export const decimalLexicalMap = (LEX) => { + ensureMatches(decimalLexicalRep, LEX); + if (noDecimalPtNumeral(LEX)) { + return noDecimalMap(LEX); + } else if (decimalPtNumeral(LEX)) { + return decimalPtMap(LEX); + } +}; + +/** + * Maps a decimal to its ·canonical representation·, a + * decimalLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a finite number. + */ +export const decimalCanonicalMap = (d) => + isIntegralNumber(decimalNumber(d)) + ? noDecimalPtCanonicalMap(d) + : decimalPtCanonicalMap(d); + +/** + * Rounds a non-zero decimal number to the nearest floating-point + * value. + * + * See . + * + * ☡ This function throws if the provided value is zero or is not a + * finite number. + * + * ※ This function uses native Ecmascript float rounding methods and + * only supports a cWidth of 24 or 53. + * + * ※ This function is not exposed. + */ +const floatingPointRound = (nV, cWidth, eMin, eMax) => { + if (!isFiniteNumber(nV) || nV == 0) { + throw new TypeError( + `सूत्र: Expected a finite nonzero number, but got: ${nV}.`, + ); + } else if (cWidth !== 24 && cWidth !== 53) { + throw new TypeError( + `सूत्र: Unsupported cWidth: ${cWidth}.`, + ); + } else if ( + cWidth === 24 && (eMin !== -149 || eMax !== 104) || + cWidth === 53 && (eMin !== -1074 || eMax !== 971) + ) { + throw new TypeError( + `सूत्र: Unsupported eMin and eMax for cWidth: ${cWidth}.`, + ); + } else { + return cWidth <= 24 ? toFloat32(nV) : +nV; + } +}; + +/** + * Maps a decimal number to that value rounded by some power of 10. + * + * See . + * + * ☡ This function throws if the provided value is not a finite number. + * + * ☡ This function throws if the provided power of 10 is not a + * nonnegative integer. + * + * ※ This function is not exposed. + */ +const round = (n, k) => { + decimalNumber(n); + nonnegativeInteger(k); + return div(n / 10 ** k + 0.5, 1) * 10 ** k; +}; + +/** + * Maps a decimal number (c × 10e) to successive approximations. + * + * See . + * + * ☡ This function throws if the provided values are not a nonnegative + * integer, an integer, an a nonnegative integer. + * + * ※ This function is not exposed. + */ +//deno-lint-ignore no-unused-vars +const floatApprox = (c, e, j) => { + nonnegativeInteger(c); + integer(e); + nonnegativeInteger(j); + return round(c, j) * 10 ** e; +}; + +/** + * Maps a floatRep onto a float value. + * + * See . + * + * ☡ This function throws if the provided value is not a floatRep + * string. + */ +export const floatLexicalMap = (LEX) => { + ensureMatches(floatRep, LEX); + if (numericalSpecialRep(LEX)) { + return specialRepValue(LEX); + } else { + const nV = noDecimalPtNumeral(LEX) + ? noDecimalMap(LEX) + : decimalPtNumeral(LEX) + ? decimalPtMap(LEX) + : scientificMap(LEX); + const v = nV == 0 ? 0 : floatingPointRound(nV, 24, -149, 104); + return v == 0 ? LEX[0] === "-" ? negativeZero : positiveZero : v; + } +}; + +/** + * Maps a doubleRep onto a double value. + * + * See . + * + * ☡ This function throws if the provided value is not a doubleRep + * string. + */ +export const doubleLexicalMap = (LEX) => { + ensureMatches(doubleRep, LEX); + if (numericalSpecialRep(LEX)) { + return specialRepValue(LEX); + } else { + const nV = noDecimalPtNumeral(LEX) + ? noDecimalMap(LEX) + : decimalPtNumeral(LEX) + ? decimalPtMap(LEX) + : scientificMap(LEX); + const v = nV == 0 ? 0 : floatingPointRound(nV, 53, -1074, 971); + return v == 0 ? LEX[0] === "-" ? negativeZero : positiveZero : v; + } +}; + +/** + * Maps a float to its ·canonical representation·, a floatRep. + * + * See . + * + * ☡ This function throws if the provided value is not a 32‐bit float. + */ +export const floatCanonicalMap = (f) => { + float(f); + return f === positiveInfinity || f === negativeInfinity || + sameValue(f, notANumber) + ? specialRepCanonicalMap(f) + : sameValue(f, 0) + ? "0.0E0" + : sameValue(f, -0) + ? "-0.0E0" + : scientificCanonicalMap(f); +}; + +/** + * Maps a double to its ·canonical representation·, a doubleRep. + * + * See . + * + * ☡ This function throws if the provided value is not a number. + */ +export const doubleCanonicalMap = (f) => { + double(f); + return f === positiveInfinity || f === negativeInfinity || + sameValue(f, notANumber) + ? specialRepCanonicalMap(f) + : sameValue(f, 0) + ? "0.0E0" + : sameValue(f, -0) + ? "-0.0E0" + : scientificCanonicalMap(f); +}; + +/** + * Maps a duYearFrag to an integer, intended as part of the value of + * the ·months· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duYearFrag + * string. + * + * ※ The X·S·D specification erroneously specifies the numeral as + * following the letter, but it precedes it. + * + * ※ This function is not exposed. + */ +const duYearFragmentMap = (Y) => + noDecimalMap(stringSlice(ensureMatches(duYearFrag, Y), 0, -1)); + +/** + * Maps a duMonthFrag to an integer, intended as part of the value of + * the ·months· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duMonthFrag + * string. + * + * ※ The X·S·D specification erroneously specifies the numeral as + * following the letter, but it precedes it. + * + * ※ This function is not exposed. + */ +const duMonthFragmentMap = (M) => + noDecimalMap(stringSlice(ensureMatches(duMonthFrag, M), 0, -1)); + +/** + * Maps a duDayFrag to an integer, intended as part of the value of the + * ·seconds· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duDayFrag + * string. + * + * ※ The X·S·D specification erroneously specifies the numeral as + * following the letter, but it precedes it. + * + * ※ This function is not exposed. + */ +const duDayFragmentMap = (D) => + noDecimalMap(stringSlice(ensureMatches(duDayFrag, D), 0, -1)); + +/** + * Maps a duHourFrag to an integer, intended as part of the value of + * the ·seconds· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duHourFrag + * string. + * + * ※ The X·S·D specification erroneously specifies the numeral as + * following the letter, but it precedes it. + * + * ※ The X·S·D specification has a copypasta typo in the definition of + * this function; it takes an argument H which necessarily is followed + * by an "H". There is no D which necessarily begins with "D". + * + * ※ This function is not exposed. + */ +const duHourFragmentMap = (H) => + noDecimalMap(stringSlice(ensureMatches(duHourFrag, H), 0, -1)); + +/** + * Maps a duMinuteFrag to an integer, intended as part of the value of + * the ·seconds· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duMinuteFrag + * string. + * + * ※ The X·S·D specification erroneously specifies the numeral as + * following the letter, but it precedes it. + * + * ※ This function is not exposed. + */ +const duMinuteFragmentMap = (M) => + noDecimalMap(stringSlice(ensureMatches(duMinuteFrag, M), 0, -1)); + +/** + * Maps a duSecondFrag to an integer, intended as part of the value of + * the ·seconds· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duMinuteFrag + * string. + * + * ※ The X·S·D specification erroneously specifies the numeral as + * following the letter, but it precedes it. + * + * ※ This function is not exposed. + */ +const duSecondFragmentMap = (S) => + getFirstSubstringIndex(ensureMatches(duSecondFrag, S), ".") !== -1 + ? decimalPtMap(stringSlice(S, 0, -1)) + : noDecimalMap(stringSlice(S, 0, -1)); + +/** + * Maps a duYearMonthFrag into an integer, intended as part of the + * ·months· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * duYearMonthFrag string. + * + * ※ This function is not exposed. + */ +const duYearMonthFragmentMap = (YM) => { + let Y, M; + new Matcher( + rawString`(?\d+Y)?(?\d+M)?`, + undefined, + (_, { groups }) => { + Y = groups.Y; + M = groups.M; + return true; + }, + )(ensureMatches(duYearMonthFrag, YM)); + const y = Y ? duYearFragmentMap(Y) : 0; + const m = M ? duMonthFragmentMap(M) : 0; + return 12 * y + m; +}; + +/** + * Maps a duTimeFrag into an integer, intended as part of the ·seconds· + * property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duTimeFrag + * string. + * + * ※ The X·S·D specification erroneously applies ·duDayFragmentMap· to + * H; ·duHourFragmentMap· is correct. + * + * ※ This function is not exposed. + */ +const duTimeFragmentMap = (T) => { + let H, M, S; + new Matcher( + rawString`T(?\d+H)?(?\d+M)?(?[\d.]+S)?`, + undefined, + (_, { groups }) => { + H = groups.H; + M = groups.M; + S = groups.S; + return true; + }, + )(ensureMatches(duTimeFrag, T)); + const h = H ? duHourFragmentMap(H) : 0; + const m = M ? duMinuteFragmentMap(M) : 0; + const s = S ? duSecondFragmentMap(S) : 0; + return 3600 * h + 60 * m + s; +}; + +/** + * Maps a duDayTimeFrag into a decimal number, which is the potential + * value of the ·seconds· property of a duration value. + * + * See . + * + * ☡ This function throws if the provided value is not a duDayTimeFrag + * string. + * + * ※ This function is not exposed. + */ +const duDayTimeFragmentMap = (DT) => { + let D, T; + new Matcher( + rawString`(?\d+D)?(?T.+)?`, + undefined, + (_, { groups }) => { + D = groups.D; + T = groups.T; + return true; + }, + )(ensureMatches(duDayTimeFrag, DT)); + const d = D ? duDayFragmentMap(D) : 0; + const t = T ? duTimeFragmentMap(T) : 0; + return 86400 * d + t; +}; + +/** + * Separates the durationLexicalRep into the month part and the seconds + * part, then maps them into the ·months· and ·seconds· of the duration + * value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * durationLexicalRep string. + */ +export const durationMap = (DUR) => { + let Y, D; + new Matcher( + rawString`-?P(?(?:\d+Y)?(?:\d+M)?)(?(?:\d+D)?(?:T.+)?)`, + undefined, + (_, { groups }) => { + Y = groups.Y; + D = groups.D; + return true; + }, + )(ensureMatches(durationLexicalRep, DUR)); + const s = DUR[0] === "-" ? -1 : 1; + return { + months: Y ? s * duYearMonthFragmentMap(Y) + 0 : 0, // cast −0 to 0 + seconds: D ? s * duDayTimeFragmentMap(D) + 0 : 0, // cast −0 to 0 + }; +}; + +/** + * Maps the lexical representation into the ·months· of a + * yearMonthDuration value. (A yearMonthDuration's ·seconds· is always + * zero.) ·yearMonthDurationMap· is a restriction of ·durationMap·. + * + * See . + * + * ☡ This function throws if the provided value is not a + * yearMonthDurationLexicalRep string. + */ +export const yearMonthDurationMap = (YM) => { + const s = YM[0] === "-" ? -1 : 1; + const Y = substring( + ensureMatches(yearMonthDurationLexicalRep, YM), + s === -1 ? 2 : 1, + ); + return { + months: s * duYearMonthFragmentMap(Y) + 0, // cast −0 to 0 + seconds: 0, + }; +}; + +/** + * Maps the lexical representation into the ·seconds· of a + * dayTimeDuration value. (A dayTimeDuration's ·months· is always + * zero.) ·dayTimeDurationMap· is a restriction of ·durationMap·. + * + * See . + * + * ☡ This function throws if the provided value is not a + * dayTimeDurationLexicalRep string. + * + * ※ The X·S·D specification erroneously describes the argument as a + * dayTimeDuration value rather than a dayTimeDurationLexicalRep. + */ +export const dayTimeDurationMap = (DT) => { + const s = DT[0] === "-" ? -1 : 1; + const D = substring( + ensureMatches(dayTimeDurationLexicalRep, DT), + s === -1 ? 2 : 1, + ); + return { + months: 0, + seconds: s * duDayTimeFragmentMap(D) + 0, // cast −0 to 0 + }; +}; + +/** + * Maps a nonnegative integer, presumably the absolute value of the + * ·months· of a duration value, to a duYearMonthFrag, a fragment of a + * duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + * + * ※ This function is not exposed. + */ +const duYearMonthCanonicalFragmentMap = (ym) => { + nonnegativeInteger(ym); + const y = div(ym, 12); + const m = mod(ym, 12); + return y !== 0 && m !== 0 + ? stringCatenate( + unsignedNoDecimalPtCanonicalMap(y), + "Y", + unsignedNoDecimalPtCanonicalMap(m), + "M", + ) + : y !== 0 + ? stringCatenate( + unsignedNoDecimalPtCanonicalMap(y), + "Y", + ) + : stringCatenate( + unsignedNoDecimalPtCanonicalMap(m), + "M", + ); +}; + +/** + * Maps a nonnegative integer, presumably the day normalized value from + * the ·seconds· of a duration value, to a duDayFrag, a fragment of a + * duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + * + * ※ Despite the description, this function does not necessarily + * return a duDayFrag (it can return an empty string). + * + * ※ This function is not exposed. + */ +const duDayCanonicalFragmentMap = (d) => + d !== 0 + ? stringCatenate( + unsignedNoDecimalPtCanonicalMap(d), + "D", + ) + : ""; + +/** + * Maps a nonnegative integer, presumably the hour normalized value + * from the ·seconds· of a duration value, to a duHourFrag, a fragment + * of a duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + * + * ※ Despite the description, this function does not necessarily + * return a duHourFrag (it can return an empty string). + * + * ※ This function is not exposed. + */ +const duHourCanonicalFragmentMap = (h) => + h !== 0 + ? stringCatenate( + unsignedNoDecimalPtCanonicalMap(h), + "H", + ) + : ""; + +/** + * Maps a nonnegative integer, presumably the minute normalized value + * from the ·seconds· of a duration value, to a duMinuteFrag, a + * fragment of a duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer. + * + * ※ Despite the description, this function does not necessarily + * return a duMinuteFrag (it can return an empty string). + * + * ※ This function is not exposed. + */ +const duMinuteCanonicalFragmentMap = (m) => + m !== 0 + ? stringCatenate( + unsignedNoDecimalPtCanonicalMap(m), + "M", + ) + : ""; + +/** + * Maps a nonnegative decimal number, presumably the second normalized + * value from the ·seconds· of a duration value, to a duSecondFrag, a + * fragment of a duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number. + * + * ※ Despite the description, this function does not necessarily + * return a duSecondFrag (it can return an empty string). + * + * ※ This function is not exposed. + */ +const duSecondCanonicalFragmentMap = (s) => + s !== 0 + ? stringCatenate( + isIntegralNumber(s) + ? unsignedNoDecimalPtCanonicalMap(s) + : unsignedDecimalPtCanonicalMap(s), + "S", + ) + : ""; + +/** + * Maps three nonnegative numbers, presumably the hour, minute, and + * second normalized values from a duration's ·seconds·, to a + * duTimeFrag, a fragment of a duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if either of the first two provided values + * are not nonnegative integers, or if the final provided value is not + * a nonnegative finite number. + * + * ※ Despite the description, this function does not necessarily + * return a duTimeFrag (it can return an empty string). + * + * ※ This function is not exposed. + */ +const duTimeCanonicalFragmentMap = (h, m, s) => + h !== 0 || m !== 0 || s !== 0 + ? stringCatenate( + "T", + duHourCanonicalFragmentMap(h), + duMinuteCanonicalFragmentMap(m), + duSecondCanonicalFragmentMap(s), + ) + : ""; + +/** + * Maps a nonnegative decimal number, presumably the absolute value of + * the ·seconds· of a duration value, to a duDayTimeFrag, a fragment of + * a duration ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number. + * + * ※ This function is not exposed. + */ +const duDayTimeCanonicalFragmentMap = (ss) => { + nonnegativeDecimalNumber(ss); + const d = div(ss, 86400); + const h = div(mod(ss, 86400), 3600); + const m = div(mod(ss, 3600), 60); + const s = mod(ss, 60); + return ss !== 0 + ? stringCatenate( + duDayCanonicalFragmentMap(d), + duTimeCanonicalFragmentMap(h, m, s), + ) + : "T0S"; +}; + +/** + * Maps a duration's property values to durationLexicalRep fragments + * and combines the fragments into a complete durationLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * duration value. + */ +export const durationCanonicalMap = (v) => { + const { months: m, seconds: s } = duration(v); + const sgn = m < 0 || s < 0 ? "-" : ""; + return m !== 0 && s !== 0 + ? stringCatenate( + sgn, + "P", + duYearMonthCanonicalFragmentMap(abs(m)), + duDayTimeCanonicalFragmentMap(abs(s)), + ) + : m !== 0 + ? stringCatenate( + sgn, + "P", + duYearMonthCanonicalFragmentMap(abs(m)), + ) + : stringCatenate( + sgn, + "P", + duDayTimeCanonicalFragmentMap(abs(s)), + ); +}; + +/** + * Maps a yearMonthDuration's ·months· value to a + * yearMonthDurationLexicalRep. (The ·seconds· value is necessarily + * zero and is ignored.) ·yearMonthDurationCanonicalMap· is a + * restriction of ·durationCanonicalMap·. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * yearMonthDuration value. + */ +export const yearMonthDurationCanonicalMap = (ym) => { + const { months: m, seconds: s } = duration(ym); + if (s !== 0) { + throw new TypeError( + `सूत्र: Expected the provided yearMonthDuration to have a value of zero for seconds, but got: ${s}.`, + ); + } else { + const sgn = m < 0 ? "-" : ""; + return stringCatenate( + sgn, + "P", + duYearMonthCanonicalFragmentMap(abs(m)), + ); + } +}; + +/** + * Maps a dayTimeDuration's ·seconds· value to a + * dayTimeDurationLexicalRep. (The ·months· value is necessarily zero + * and is ignored.) ·dayTimeDurationCanonicalMap· is a restriction of + * ·durationCanonicalMap·. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * dayTimeDuration value. + */ +export const dayTimeDurationCanonicalMap = (dt) => { + const { months: m, seconds: s } = duration(dt); + if (m !== 0) { + throw new TypeError( + `सूत्र: Expected the provided dayTimeDuration to have a value of zero for months, but got: ${m}.`, + ); + } else { + const sgn = s < 0 ? "-" : ""; + return stringCatenate( + sgn, + "P", + duDayTimeCanonicalFragmentMap(abs(s)), + ); + } +}; + +/** + * If month (mo) is out of range, adjust month and year (yr) + * accordingly; otherwise, make no change. + * + * See . + * + * ☡ This function throws if the provided values are not integers. + * + * ※ This function is not exposed. + */ +const normalizeMonth = (yr, mo) => { + integer(yr); + integer(mo); + return [yr + div(mo - 1, 12), mod(mo - 1, 12) + 1]; +}; + +/** + * If month (mo) is out of range, or day (da) is out of range for the + * appropriate month, then adjust values accordingly, otherwise make no + * change. + * + * See . + * + * ☡ This function throws if the provided values are not integers. + * + * ※ This function is not exposed. + */ +const normalizeDay = (yr, mo, da) => { + integer(yr); + integer(mo); + integer(da); + [yr, mo] = normalizeMonth(yr, mo); + let limit = daysInMonth(yr, mo); + while (da > limit || da <= 0) { + if (da > limit) { + da -= limit; + mo += 1; + [yr, mo] = normalizeMonth(yr, mo); + limit = daysInMonth(yr, mo); + } + if (da <= 0) { + mo -= 1; + [yr, mo] = normalizeMonth(yr, mo); + limit = daysInMonth(yr, mo); + da += limit; + } + } + return [yr, mo, da]; +}; + +/** + * Normalizes minute, hour, month, and year values to values that obey + * the appropriate constraints. + * + * See . + * + * ☡ This function throws if the provided values are not integers. + * + * ※ This function is not exposed. + */ +const normalizeMinute = (yr, mo, da, hr, mi) => { + integer(yr); + integer(mo); + integer(da); + integer(hr); + integer(mi); + hr += div(mi, 60); + mi = mod(mi, 60); + da += div(hr, 24); + hr = mod(hr, 24); + [yr, mo, da] = normalizeDay(yr, mo, da); + return [yr, mo, da, hr, mi]; +}; + +/** + * Normalizes minute, hour, month, and year values to values that obey + * the appropriate constraints. (This algorithm ignores leap seconds.) + * + * See . + * + * ☡ This function throws if the first five provided values are not + * integers or the sixth is not a finite number. + * + * ※ This function is not exposed. + */ +const normalizeSecond = (yr, mo, da, hr, mi, se) => { + integer(yr); + integer(mo); + integer(da); + integer(hr); + integer(mi); + decimalNumber(se); + mi += div(se, 60); + se = mod(se, 60); + [yr, mo, da, hr, mi] = normalizeMinute(yr, mo, da, hr, mi); + return [yr, mo, da, hr, mi, se]; +}; + +/** + * Normalizes minute, hour, month, and year values to values that obey + * the appropriate constraints. (This algorithm ignores leap seconds.) + * + * See . + * + * ☡ This function throws if the first value is not absent or an + * integer or if the second value is not an integer between 1 and 12 + * inclusive. + */ +export const daysInMonth = (y, m) => { + if (y !== absent && !isIntegralNumber(y)) { + throw new TypeError( + `सूत्र: Expected an optional integer, but got: ${y}.`, + ); + } else if (!(isIntegralNumber(m) && m >= 1 && m <= 12)) { + throw new TypeError( + `सूत्र: Expected an integer between 1 and 12 inclusive, but got: ${m}.`, + ); + } else { + return m === 2 + ? y === absent || y % 4 || !(y % 100) && y % 400 ? 28 : 29 + : m === 4 || m === 6 || m === 9 || m === 11 + ? 30 + : 31; + } +}; + +/** + * Returns an instance of the date/timeSevenPropertyModel with property + * values as specified in the arguments. If an argument is omitted, the + * corresponding property is set to absent. + * + * See . + * + * ※ The X·S·D specification erroneously fails to account for the + * value `"--02-29"`, which `·newDateTime·` treats as 01 March because + * it assumes February has 28 days when the year is absent. This is a + * correct assumption generally, but February should be treated as + * having 29 days when a value of `"--02-29"` is explicity given. + * + * ☡ This function throws if the provided values do not match the + * ranges expected by the Date∕time Seven‐Property Model. + */ +export const newDateTime = (Yr, Mo, Da, Hr, Mi, Se, Tz) => { + if (Yr !== absent && !isIntegralNumber(Yr)) { + throw new TypeError( + `सूत्र: Expected an optional integer, but got: ${Yr}.`, + ); + } else if ( + Mo !== absent && !(isIntegralNumber(Mo) && Mo >= 1 && Mo <= 12) + ) { + throw new TypeError( + `सूत्र: Expected an optional integer between 1 and 12 inclusive, but got: ${Mo}.`, + ); + } else if ( + Da !== absent && !(isIntegralNumber(Da) && Da >= 1 && Da <= 31) + ) { + throw new TypeError( + `सूत्र: Expected an optional integer between 1 and 31 inclusive, but got: ${Da}.`, + ); + } else if ( + Hr !== absent && !(isIntegralNumber(Hr) && Hr >= 0 && Hr <= 24) + ) { + throw new TypeError( + `सूत्र: Expected an optional integer between 0 and 24 inclusive, but got: ${Hr}.`, + ); + } else if ( + Mi !== absent && !(isIntegralNumber(Mi) && Mi >= 0 && Mi <= 59) + ) { + throw new TypeError( + `सूत्र: Expected an optional integer between 0 and 59 inclusive, but got: ${Mi}.`, + ); + } else if ( + Se !== absent && !(isFiniteNumber(Se) && Se >= 0 && Se < 60) + ) { + throw new TypeError( + `सूत्र: Expected an optional finite number greater than or equal to 0 and less than 60, but got: ${Se}.`, + ); + } else if ( + Tz !== absent && !(isIntegralNumber(Tz) && Tz >= -840 && Tz <= 840) + ) { + throw new TypeError( + `सूत्र: Expected an optional integer between -840 and 840 inclusive, but got: ${Tz}.`, + ); + } else { + let yr = Yr !== absent ? Yr : Mo === 2 && Da === 29 ? 0 : 1; + let mo = Mo !== absent ? Mo : 1; + let da = Da !== absent ? Da : 1; + let hr = Hr !== absent ? Hr : 0; + let mi = Mi !== absent ? Mi : 0; + let se = Se !== absent ? Se : 0; + [yr, mo, da, hr, mi, se] = normalizeSecond(yr, mo, da, hr, mi, se); + return { + year: Yr === absent ? absent : yr, + month: Mo === absent ? absent : mo, + day: Da === absent ? absent : da, + hour: Hr === absent ? absent : hr, + minute: Mi === absent ? absent : mi, + second: Se === absent ? absent : se, + timezoneOffset: Tz, + }; + } +}; + +/** + * Adds a duration to a dateTime value, producing another dateTime + * value. + * + * See . + * + * ※ The X·S·D specification erroneously fails to account for the + * value `"--02-29"`, which `·dateTimePlusDuration·` treats as 01 March + * because it assumes February has 28 days when the year is absent. + * This is a correct assumption generally, but February should be + * treated as having 29 days when a value of `"--02-29"` is explicity + * given. + * + * ☡ This function throws if the provided duration or + * date/timeSevenPropertyModel values are not valid. + * + * ※ Despite the name and description, this function can add a + * duration to any date/timeSevenPropertyModel value. + */ +export const dateTimePlusDuration = (du, dt) => { + const { months, seconds } = duration(du); + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(dt); + let yr = year !== absent ? year : month === 2 && day === 29 ? 0 : 1; + let mo = month !== absent ? month : 1; + let da = day !== absent ? day : 1; + let hr = hour !== absent ? hour : 0; + let mi = minute !== absent ? minute : 0; + let se = second !== absent ? second : 0; + mo += months; + [yr, mo] = normalizeMonth(yr, mo); + da = min(da, daysInMonth(yr, mo)); + se += seconds; + [yr, mo, da, hr, mi, se] = normalizeSecond(yr, mo, da, hr, mi, se); + return newDateTime( + year === absent ? absent : yr, + month === absent ? absent : mo, + day === absent ? absent : da, + hour === absent ? absent : hr, + minute === absent ? absent : mi, + second === absent ? absent : se, + timezoneOffset, + ); +}; + +/** + * Maps a date/timeSevenPropertyModel value to the decimal number + * representing its position on the "time line". + * + * See . + * + * ☡ This function throws if the provided date/timeSevenPropertyModel + * value is not valid. + */ +export const timeOnTimeline = (dt) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(dt); + const yr = year === absent ? 1971 : year - 1; + const mo = month === absent ? 12 : month; + const da = day === absent ? daysInMonth(yr + 1, mo) - 1 : day - 1; + const hr = hour === absent ? 0 : hour; + const mi = (minute === absent ? 0 : minute) - + (timezoneOffset === absent ? 0 : timezoneOffset); + const se = second === absent ? 0 : second; + let ToTI = 31536000 * yr; + ToTI += 86400 * (div(yr, 400) - div(yr, 100) + div(yr, 4)); + for (let m = 1; m < mo; ++m) { + ToTI += 86400 * daysInMonth(yr + 1, m); + } + ToTI += 86400 * da; + ToTI += 3600 * hr + 60 * mi + se; + return ToTI; +}; + +/** + * Maps a yearFrag, part of a date/timeSevenPropertyModel's ·lexical + * representation·, onto an integer, presumably the ·year· property of + * a date/timeSevenPropertyModel value. + * + * See . + * + * ☡ This function throws if the provided value is not a yearFrag + * string. + */ +export const yearFragValue = (YR) => + noDecimalMap(ensureMatches(yearFrag, YR)); + +/** + * Maps a monthFrag, part of a date/timeSevenPropertyModel's ·lexical + * representation·, onto an integer, presumably the ·month· property of + * a date/timeSevenPropertyModel value. + * + * See . + * + * ☡ This function throws if the provided value is not a monthFrag + * string. + */ +export const monthFragValue = (MO) => + unsignedNoDecimalMap(ensureMatches(monthFrag, MO)); + +/** + * Maps a dayFrag, part of a date/timeSevenPropertyModel's ·lexical + * representation·, onto an integer, presumably the ·day· property of a + * date/timeSevenPropertyModel value. + * + * See . + * + * ☡ This function throws if the provided value is not a dayFrag + * string. + */ +export const dayFragValue = (DA) => + unsignedNoDecimalMap(ensureMatches(dayFrag, DA)); + +/** + * Maps a hourFrag, part of a date/timeSevenPropertyModel's ·lexical + * representation·, onto an integer, presumably the ·hour· property of + * a date/timeSevenPropertyModel value. + * + * See . + * + * ☡ This function throws if the provided value is not a hourFrag + * string. + */ +export const hourFragValue = (HR) => + unsignedNoDecimalMap(ensureMatches(hourFrag, HR)); + +/** + * Maps a minuteFrag, part of a date/timeSevenPropertyModel's ·lexical + * representation·, onto an integer, presumably the ·minute· property + * of a date/timeSevenPropertyModel value. + * + * See . + * + * ☡ This function throws if the provided value is not a minuteFrag + * string. + */ +export const minuteFragValue = (MI) => + unsignedNoDecimalMap(ensureMatches(minuteFrag, MI)); + +/** + * Maps a secondFrag, part of a date/timeSevenPropertyModel's ·lexical + * representation·, onto a decimal number, presumably the ·second· + * property of a date/timeSevenPropertyModel value. + * + * See . + * + * ☡ This function throws if the provided value is not a secondFrag + * string. + */ +export const secondFragValue = (SE) => + getFirstSubstringIndex(ensureMatches(secondFrag, SE), ".") === -1 + ? unsignedNoDecimalMap(SE) + : unsignedDecimalPtMap(SE); + +/** + * Maps a timezoneFrag, part of a date/timeSevenPropertyModel's + * ·lexical representation·, onto an integer, presumably the + * ·timezoneOffset· property of a date/timeSevenPropertyModel value. + * + * See . + * + * ※ The X·S·D specification erroneously specifies + * ·unsignedDecimalPtMap·(H) and ·unsignedDecimalPtMap·(M), but + * ·unsignedNoDecimalMap·(H) and ·unsignedDecimalPtMap·(M) is correct. + * + * ☡ This function throws if the provided value is not a timezoneFrag + * string. + */ +export const timezoneFragValue = (TZ) => { + const s = ensureMatches(timezoneFrag, TZ)[0]; + if (s === "Z") { + return 0; + } else { + let H, M; + new Matcher( + rawString`[+-](?\d{2}):(?\d{2})`, + undefined, + (_, { groups }) => { + H = groups.H; + M = groups.M; + return true; + }, + )(TZ); + return s === "-" + ? -(unsignedNoDecimalMap(H) * 60 + unsignedNoDecimalMap(M)) + 0 + : unsignedNoDecimalMap(H) * 60 + unsignedNoDecimalMap(M); + } +}; + +/** + * Maps a dateTimeLexicalRep to a dateTime value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * dateTimeLexicalRep string. + */ +export const dateTimeLexicalMap = (LEX) => { + let Y, MO, D, H, MI, S, T; + new Matcher( + rawString`(?-?\d+)-(?\d+)-(?\d+)T(?:24:00:00(?:\.0+)?|(?\d+):(?\d+):(?[\d.]+))(?.+)?`, + undefined, + (_, { groups }) => { + Y = groups.Y; + MO = groups.MO; + D = groups.D; + H = groups.H ?? absent; + MI = groups.MI ?? absent; + S = groups.S ?? absent; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(dateTimeLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return H === absent + ? newDateTime( + yearFragValue(Y), + monthFragValue(MO), + dayFragValue(D), + 24, + 0, + 0, + tz, + ) + : newDateTime( + yearFragValue(Y), + monthFragValue(MO), + dayFragValue(D), + hourFragValue(H), + minuteFragValue(MI), + secondFragValue(S), + tz, + ); +}; + +/** + * Maps a timeLexicalMap to a time value. + * + * See . + * + * ☡ This function throws if the provided value is not a timeLexicalMap + * string. + */ +export const timeLexicalMap = (LEX) => { + let H, MI, S, T; + new Matcher( + rawString`(?:24:00:00(?:\.0+)?|(?\d+):(?\d+):(?[\d.]+))(?.+)?`, + undefined, + (_, { groups }) => { + H = groups.H ?? absent; + MI = groups.MI ?? absent; + S = groups.S ?? absent; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(timeLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return H === absent + ? newDateTime( + absent, + absent, + absent, + 0, + 0, + 0, + tz, + ) + : newDateTime( + absent, + absent, + absent, + hourFragValue(H), + minuteFragValue(MI), + secondFragValue(S), + tz, + ); +}; + +/** + * Maps a dateLexicalRep to a date value. + * + * See . + * + * ☡ This function throws if the provided value is not a dateLexicalRep + * string. + */ +export const dateLexicalMap = (LEX) => { + let Y, M, D, T; + new Matcher( + rawString`(?-?\d+)-(?\d+)-(?\d+)(?.+)?`, + undefined, + (_, { groups }) => { + Y = groups.Y; + M = groups.M; + D = groups.D; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(dateLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return newDateTime( + yearFragValue(Y), + monthFragValue(M), + dayFragValue(D), + absent, + absent, + absent, + tz, + ); +}; + +/** + * Maps a gYearMonthLexicalRep to a date value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * gYearMonthLexicalRep string. + */ +export const gYearMonthLexicalMap = (LEX) => { + let Y, M, T; + new Matcher( + rawString`(?-?\d+)-(?\d+)(?.+)?`, + undefined, + (_, { groups }) => { + Y = groups.Y; + M = groups.M; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(gYearMonthLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return newDateTime( + yearFragValue(Y), + monthFragValue(M), + absent, + absent, + absent, + absent, + tz, + ); +}; + +/** + * Maps a gYearLexicalRep to a date value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * gYearLexicalRep string. + */ +export const gYearLexicalMap = (LEX) => { + let Y, T; + new Matcher( + rawString`(?-?\d+)(?.+)?`, + undefined, + (_, { groups }) => { + Y = groups.Y; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(gYearLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return newDateTime( + yearFragValue(Y), + absent, + absent, + absent, + absent, + absent, + tz, + ); +}; + +/** + * Maps a gMonthDayLexicalRep to a date value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * gMonthDayLexicalRep string. + */ +export const gMonthDayLexicalMap = (LEX) => { + let M, D, T; + new Matcher( + rawString`--(?\d+)-(?\d+)(?.+)?`, + undefined, + (_, { groups }) => { + M = groups.M; + D = groups.D; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(gMonthDayLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return newDateTime( + absent, + monthFragValue(M), + dayFragValue(D), + absent, + absent, + absent, + tz, + ); +}; + +/** + * Maps a gDayLexicalRep to a date value. + * + * See . + * + * ☡ This function throws if the provided value is not a gDayLexicalRep + * string. + */ +export const gDayLexicalMap = (LEX) => { + let D, T; + new Matcher( + rawString`---(?\d+)(?.+)?`, + undefined, + (_, { groups }) => { + D = groups.D; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(gDayLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return newDateTime( + absent, + absent, + dayFragValue(D), + absent, + absent, + absent, + tz, + ); +}; + +/** + * Maps a gMonthLexicalRep to a date value. + * + * See . + * + * ☡ This function throws if the provided value is not a + * gMonthLexicalRep string. + */ +export const gMonthLexicalMap = (LEX) => { + let M, T; + new Matcher( + rawString`--(?\d+)(?.+)?`, + undefined, + (_, { groups }) => { + M = groups.M; + T = groups.T ?? absent; + return true; + }, + )(ensureMatches(gMonthLexicalRep, LEX)); + const tz = T === absent ? absent : timezoneFragValue(T); + return newDateTime( + absent, + monthFragValue(M), + absent, + absent, + absent, + absent, + tz, + ); +}; + +/** + * Maps a nonnegative integer less than 100 onto an unsigned + * always-two-digit numeral. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * integer less than 100. + * + * ※ This function is not exposed. + */ +const unsTwoDigitCanonicalFragmentMap = (i) => { + if (!(isIntegralNumber(i) && i >= 0 && i < 100)) { + throw new TypeError( + `सूत्र: Expected a nonnegative integer less than 100, but got: ${i}.`, + ); + } else { + return stringCatenate(digit(div(i, 10)), digit(mod(i, 10))); + } +}; + +/** + * Maps an integer between -10000 and 10000 onto an always-four-digit numeral. + * + * See . + * + * ☡ This function throws if the provided value is not an integer whose + * absolute value is less than 10000. + * + * ※ This function is not exposed. + */ +const fourDigitCanonicalFragmentMap = (i) => { + if (!(isIntegralNumber(i) && abs(i) < 10000)) { + throw new TypeError( + `सूत्र: Expected an integer whose absolute value is less than 10000, but got: ${i}.`, + ); + } else { + return i < 0 + ? stringCatenate( + "-", + unsTwoDigitCanonicalFragmentMap(div(-i, 100)), + unsTwoDigitCanonicalFragmentMap(mod(-i, 100)), + ) + : stringCatenate( + unsTwoDigitCanonicalFragmentMap(div(i, 100)), + unsTwoDigitCanonicalFragmentMap(mod(i, 100)), + ); + } +}; + +/** + * Maps an integer, presumably the ·year· property of a + * date/timeSevenPropertyModel value, onto a yearFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer. + */ +export const yearCanonicalFragmentMap = (y) => + abs(integer(y)) > 9999 + ? noDecimalPtCanonicalMap(y) + : fourDigitCanonicalFragmentMap(y); + +/** + * Maps an integer, presumably the ·month· property of a + * date/timeSevenPropertyModel value, onto a monthFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer + * between 1 and 12 inclusive. + */ +export const monthCanonicalFragmentMap = (m) => { + if (!(isIntegralNumber(m) && m >= 1 && m <= 12)) { + throw new TypeError( + `सूत्र: Expected an integer between 1 and 12 inclusive, but got: ${m}.`, + ); + } else { + return unsTwoDigitCanonicalFragmentMap(m); + } +}; + +/** + * Maps an integer, presumably the ·day· property of a + * date/timeSevenPropertyModel value, onto a dayFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer + * between 1 and 31 inclusive. + */ +export const dayCanonicalFragmentMap = (d) => { + if (!(isIntegralNumber(d) && d >= 1 && d <= 31)) { + throw new TypeError( + `सूत्र: Expected an integer between 1 and 31 inclusive, but got: ${d}.`, + ); + } else { + return unsTwoDigitCanonicalFragmentMap(d); + } +}; + +/** + * Maps an integer, presumably the ·hour· property of a + * date/timeSevenPropertyModel value, onto an hourFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer + * between 0 and 23 inclusive. + */ +export const hourCanonicalFragmentMap = (h) => { + if (!(isIntegralNumber(h) && h >= 0 && h <= 23)) { + throw new TypeError( + `सूत्र: Expected an integer between 0 and 23 inclusive, but got: ${h}.`, + ); + } else { + return unsTwoDigitCanonicalFragmentMap(h); + } +}; + +/** + * Maps an integer, presumably the ·minute· property of a + * date/timeSevenPropertyModel value, onto a minuteFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer + * between 0 and 59 inclusive. + */ +export const minuteCanonicalFragmentMap = (m) => { + if (!(isIntegralNumber(m) && m >= 0 && m <= 59)) { + throw new TypeError( + `सूत्र: Expected an integer between 0 and 59 inclusive, but got: ${m}.`, + ); + } else { + return unsTwoDigitCanonicalFragmentMap(m); + } +}; + +/** + * Maps a decimal number, presumably the ·second· property of a + * date/timeSevenPropertyModel value, onto a secondFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not a nonnegative + * finite number less than 60. + * + * ※ The X·S·D specification identifies the argument to this function + * as “a nonnegative decimal number less than 70”, but second values 60 + * or larger are not supported by X·S·D. + * + * ※ This function makes use of the built·in Ecmascript float + * serialization to avoid rounding errors. + */ +export const secondCanonicalFragmentMap = (s) => { + if (!(isFiniteNumber(s) && s < 60)) { + throw new TypeError( + `सूत्र: Expected a nonnegative finite number less than 60, but got: ${s}.`, + ); + } else { + // return isIntegralNumber(s) + // ? unsTwoDigitCanonicalFragmentMap(s) + // : stringCatenate( + // unsTwoDigitCanonicalFragmentMap(div(s, 1)), + // ".", + // fractionDigitsCanonicalFragmentMap(mod(s, 1)), + // ); + return isIntegralNumber(s) + ? unsTwoDigitCanonicalFragmentMap(s) + : s < 10 + ? stringCatenate("0", decimalPtCanonicalMap(s)) + : decimalPtCanonicalMap(s); + } +}; + +/** + * Maps an integer, presumably the ·timezoneOffset· property of a + * date/timeSevenPropertyModel value, onto a timezoneFrag, part of a + * date/timeSevenPropertyModel's ·lexical representation·. + * + * See . + * + * ☡ This function throws if the provided value is not an integer + * between −840 and 840 inclusive. + */ +export const timezoneCanonicalFragmentMap = (t) => { + if (!(isIntegralNumber(t) && t >= -840 && t <= 840)) { + throw new TypeError( + `सूत्र: Expected an integer between −840 and 840 inclusive, but got: ${t}.`, + ); + } else { + return t === 0 ? "Z" : t < 0 + ? stringCatenate( + "-", + unsTwoDigitCanonicalFragmentMap(div(-t, 60)), + ":", + unsTwoDigitCanonicalFragmentMap(mod(-t, 60)), + ) + : stringCatenate( + "+", + unsTwoDigitCanonicalFragmentMap(div(t, 60)), + ":", + unsTwoDigitCanonicalFragmentMap(mod(t, 60)), + ); + } +}; + +/** + * Maps a dateTime value to a dateTimeLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * dateTime. + */ +export const dateTimeCanonicalMap = (dt) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(dt); + ensurePresence("year", year, true); + ensurePresence("month", month, true); + ensurePresence("day", day, true); + ensurePresence("hour", hour, true); + ensurePresence("minute", minute, true); + ensurePresence("second", second, true); + const DT = stringCatenate( + yearCanonicalFragmentMap(year), + "-", + monthCanonicalFragmentMap(month), + "-", + dayCanonicalFragmentMap(day), + "T", + hourCanonicalFragmentMap(hour), + ":", + minuteCanonicalFragmentMap(minute), + ":", + secondCanonicalFragmentMap(second), + ); + return timezoneOffset === absent + ? DT + : stringCatenate(DT, timezoneCanonicalFragmentMap(timezoneOffset)); +}; + +/** + * Maps a time value to a timeLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete time. + */ +export const timeCanonicalMap = (ti) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(ti); + ensurePresence("year", year, false); + ensurePresence("month", month, false); + ensurePresence("day", day, false); + ensurePresence("hour", hour, true); + ensurePresence("minute", minute, true); + ensurePresence("second", second, true); + const T = stringCatenate( + hourCanonicalFragmentMap(hour), + ":", + minuteCanonicalFragmentMap(minute), + ":", + secondCanonicalFragmentMap(second), + ); + return timezoneOffset === absent + ? T + : stringCatenate(T, timezoneCanonicalFragmentMap(timezoneOffset)); +}; + +/** + * Maps a date value to a dateLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete date. + */ +export const dateCanonicalMap = (da) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(da); + ensurePresence("year", year, true); + ensurePresence("month", month, true); + ensurePresence("day", day, true); + ensurePresence("hour", hour, false); + ensurePresence("minute", minute, false); + ensurePresence("second", second, false); + const D = stringCatenate( + yearCanonicalFragmentMap(year), + "-", + monthCanonicalFragmentMap(month), + "-", + dayCanonicalFragmentMap(day), + ); + return timezoneOffset === absent + ? D + : stringCatenate(D, timezoneCanonicalFragmentMap(timezoneOffset)); +}; + +/** + * Maps a gYearMonth value to a gYearMonthLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * gYearMonth. + */ +export const gYearMonthCanonicalMap = (ym) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(ym); + ensurePresence("year", year, true); + ensurePresence("month", month, true); + ensurePresence("day", day, false); + ensurePresence("hour", hour, false); + ensurePresence("minute", minute, false); + ensurePresence("second", second, false); + const YM = stringCatenate( + yearCanonicalFragmentMap(year), + "-", + monthCanonicalFragmentMap(month), + ); + return timezoneOffset === absent + ? YM + : stringCatenate(YM, timezoneCanonicalFragmentMap(timezoneOffset)); +}; + +/** + * Maps a gYear value to a gYearLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * gYear. + */ +export const gYearCanonicalMap = (gY) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(gY); + ensurePresence("year", year, true); + ensurePresence("month", month, false); + ensurePresence("day", day, false); + ensurePresence("hour", hour, false); + ensurePresence("minute", minute, false); + ensurePresence("second", second, false); + return timezoneOffset === absent + ? yearCanonicalFragmentMap(year) + : stringCatenate( + yearCanonicalFragmentMap(year), + timezoneCanonicalFragmentMap(timezoneOffset), + ); +}; + +/** + * Maps a gMonthDay value to a gMonthDayLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * gMonthDay. + */ +export const gMonthDayCanonicalMap = (md) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(md); + ensurePresence("year", year, false); + ensurePresence("month", month, true); + ensurePresence("day", day, true); + ensurePresence("hour", hour, false); + ensurePresence("minute", minute, false); + ensurePresence("second", second, false); + const MD = stringCatenate( + "--", + monthCanonicalFragmentMap(month), + "-", + dayCanonicalFragmentMap(day), + ); + return timezoneOffset === absent + ? MD + : stringCatenate(MD, timezoneCanonicalFragmentMap(timezoneOffset)); +}; + +/** + * Maps a gDay value to a gDayLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete gDay. + */ +export const gDayCanonicalMap = (gD) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(gD); + ensurePresence("year", year, false); + ensurePresence("month", month, false); + ensurePresence("day", day, true); + ensurePresence("hour", hour, false); + ensurePresence("minute", minute, false); + ensurePresence("second", second, false); + return timezoneOffset === absent + ? stringCatenate("---", dayCanonicalFragmentMap(day)) + : stringCatenate( + "---", + dayCanonicalFragmentMap(day), + timezoneCanonicalFragmentMap(timezoneOffset), + ); +}; + +/** + * Maps a gMonth value to a gMonthLexicalRep. + * + * See . + * + * ☡ This function throws if the provided value is not a complete + * gMonth. + */ +export const gMonthCanonicalMap = (gM) => { + const { + year, + month, + day, + hour, + minute, + second, + timezoneOffset, + } = date·timeSevenPropertyModel(gM); + ensurePresence("year", year, false); + ensurePresence("month", month, true); + ensurePresence("day", day, false); + ensurePresence("hour", hour, false); + ensurePresence("minute", minute, false); + ensurePresence("second", second, false); + return timezoneOffset === absent + ? stringCatenate("--", dayCanonicalFragmentMap(month)) + : stringCatenate( + "--", + dayCanonicalFragmentMap(month), + timezoneCanonicalFragmentMap(timezoneOffset), + ); +}; + +/** + * Maps a ·literal· matching the stringRep production to a string + * value. + * + * See . + * + * ☡ This function throws if the provided value is not a stringRep + * string. + */ +export const stringLexicalMap = (LEX) => ensureMatches(stringRep, LEX); + +/** + * Maps a ·literal· matching the booleanRep production to a boolean + * value. + * + * See . + * + * ☡ This function throws if the provided value is not a booleanRep + * string. + */ +export const booleanLexicalMap = (LEX) => { + ensureMatches(booleanRep, LEX); + return LEX === "true" || LEX === "1"; +}; + +/** + * Maps a string value to a stringRep. + * + * See . + * + * ☡ This function throws if the provided value is not a valid string. + */ +export const stringCanonicalMap = (s) => ensureMatches(stringRep, s); + +/** + * Maps a boolean value to a booleanRep. + * + * See . + * + * ☡ This function throws if the provided value is not a boolean. + */ +export const booleanCanonicalMap = (b) => { + if (typeof b !== "boolean") { + throw new TypeError(`सूत्र: Expected a boolean, but got: ${b}.`); + } else { + return b ? "true" : "false"; + } +}; + +/** + * Maps a ·literal· matching the hexBinary production to a sequence of + * octets in the form of a hexBinary value. + * + * See . + * + * ☡ This function throws if the provided value is not a hexBinary + * string. + * + * ※ This function returns an `ArrayBuffer`. + */ +export const hexBinaryMap = (LEX) => { + ensureMatches(hexBinary, LEX); + const bufferLength = LEX.length / 2; + const o = new ArrayBuffer(bufferLength); + for (let bufferIndex = 0, index = 0; bufferIndex < bufferLength;) { + set8BitIntegralItem( + o, + bufferIndex, + hexOctetMap(substring(LEX, index, index = ++bufferIndex * 2)), + ); + } + return o; +}; + +/** + * Maps a ·literal· matching the hexOctet production to a single octet. + * + * See . + * + * ☡ This function throws if the provided value is not a hexOctet + * string. + * + * ※ This function is not exposed. + */ +const hexOctetMap = (LEX) => { + const [d0, d1] = ensureMatches(hexOctet, LEX); + let octet = 0; + let bit = 8; + for (const digit of hexDigitMap(d0)) { + octet |= digit << --bit; + } + for (const digit of hexDigitMap(d1)) { + octet |= digit << --bit; + } + return octet; +}; + +/** + * Maps a hexadecimal digit (a character matching the hexDigit + * production) to a sequence of four binary digits. + * + * See . + * + * ☡ This function throws if the provided value is not a hexDigit + * string. + * + * ※ It would be way simpler and more efficient to just return a + * single value here rather than a sequence of digits. + * + * ※ This function is not exposed. + */ +const hexDigitMap = (d) => { + switch (ensureMatches(hexDigit, d)) { + case "0": + return binaryDigitSequence("0000"); + case "1": + return binaryDigitSequence("0001"); + case "2": + return binaryDigitSequence("0010"); + case "3": + return binaryDigitSequence("0011"); + case "4": + return binaryDigitSequence("0100"); + case "5": + return binaryDigitSequence("0101"); + case "6": + return binaryDigitSequence("0110"); + case "7": + return binaryDigitSequence("0111"); + case "8": + return binaryDigitSequence("1000"); + case "9": + return binaryDigitSequence("1001"); + case "A": + case "a": + return binaryDigitSequence("1010"); + case "B": + case "b": + return binaryDigitSequence("1011"); + case "C": + case "c": + return binaryDigitSequence("1100"); + case "D": + case "d": + return binaryDigitSequence("1101"); + case "E": + case "e": + return binaryDigitSequence("1110"); + case "F": + case "f": + return binaryDigitSequence("1111"); + } +}; + +/** + * Maps a hexBinary value to a literal matching the hexBinary + * production. + * + * See . + * + * ☡ This function throws if the provided value is not an + * `ArrayBuffer`. + */ +export const hexBinaryCanonical = (o) => { + hexBinaryValue(o); + const length = getByteLength(o); + return stringCatenate( + "", + ...generatorSequence(function* () { + for (let index = 0; index < length; ++index) { + yield hexOctetCanonical( + toNumber(get8BitUnsignedIntegralItem(o, index)), + ); + } + }), + ); +}; + +/** + * Maps a binary octet to a literal matching the hexOctet production. + * + * See . + * + * ☡ This function throws if the provided value is not a hexOctet + * string. + * + * ※ This function is not exposed. + */ +const hexOctetCanonical = (o) => { + if (!(isIntegralNumber(o) && o >= 0 && o <= 0xFF)) { + throw new TypeError( + `सूत्र: Expected a binary octet but got: ${o}.`, + ); + } else { + const lo = generatorSequence(function* () { + for (let i = 1; i <= 4; ++i) { + const offset = 4 - i; + yield (o & 1 << offset) >> offset; + } + }); + const hi = generatorSequence(function* () { + for (let i = 1; i <= 4; ++i) { + const offset = 8 - i; + yield (o & 1 << offset) >> offset; + } + }); + return hexDigitCanonical(hi) + hexDigitCanonical(lo); + } +}; + +/** + * Maps a four-bit sequence to a hexadecimal digit (a literal matching + * the hexDigit production). + * + * See . + * + * ☡ This function throws if the provided value is not an iterable of + * binary digits. + * + * ※ It would be way simpler and more efficient to just call this with + * a single value rather than a sequence of digits. + * + * ※ This function is not exposed. + */ +const hexDigitCanonical = (d) => { + let sum = 0; + for (const digit of d) { + if (digit != 0 && digit != 1) { + throw new TypeError( + `सूत्र: Expected a binary digit but got: ${digit}.`, + ); + } else { + sum = (sum << 1) | digit; + } + } + switch (sum) { + case 0b0000: + return "0"; + case 0b0001: + return "1"; + case 0b0010: + return "2"; + case 0b0011: + return "3"; + case 0b0100: + return "4"; + case 0b0101: + return "5"; + case 0b0110: + return "6"; + case 0b0111: + return "7"; + case 0b1000: + return "8"; + case 0b1001: + return "9"; + case 0b1010: + return "A"; + case 0b1011: + return "B"; + case 0b1100: + return "C"; + case 0b1101: + return "D"; + case 0b1110: + return "E"; + case 0b1111: + return "F"; + } +};