X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/5255537dba0f1fceaf0bc50c93ae5c4e6a2af66b..HEAD:/string.js diff --git a/string.js b/string.js index 0c603ac..e8126d1 100644 --- a/string.js +++ b/string.js @@ -19,10 +19,14 @@ import { stringIteratorFunction, } from "./iterable.js"; import { + defineOwnDataProperty, defineOwnProperties, getOwnPropertyDescriptors, + objectCreate, + setPropertyValues, setPrototype, } from "./object.js"; +import { sameValue, toLength, UNDEFINED } from "./value.js"; const RE = RegExp; const { prototype: rePrototype } = RE; @@ -93,7 +97,7 @@ export const { * expression with `^(?:` and `)$` if you don’t want nongreedy * regular expressions to fail when shorter matches are possible. */ - constructor(source, name = undefined, constraint = null) { + constructor(source, name = UNDEFINED, constraint = null) { super( ($) => { if (typeof $ !== "string") { @@ -138,17 +142,19 @@ export const { return defineOwnProperties( setPrototype(this, matcherPrototype), { - lastIndex: { + lastIndex: setPropertyValues(objectCreate(null), { configurable: false, enumerable: false, value: 0, writable: false, - }, - name: { - value: name != null + }), + name: defineOwnDataProperty( + objectCreate(null), + "value", + name != null ? `${name}` : `Matcher(${call(reToString, regExp, [])})`, - }, + ), }, ); } @@ -246,21 +252,31 @@ export const { } }; - const matcherConstructor = defineOwnProperties( + const matcherConstructor = Object.defineProperties( class extends RegExp { constructor(...args) { return new Matcher(...args); } }, { - name: { value: "Matcher" }, - length: { value: 1 }, + name: defineOwnDataProperty( + Object.create(null), + "value", + "Matcher", + ), + length: defineOwnDataProperty(Object.create(null), "value", 1), }, ); const matcherPrototype = defineOwnProperties( matcherConstructor.prototype, getOwnPropertyDescriptors(Matcher.prototype), - { constructor: { value: matcherConstructor } }, + { + constructor: defineOwnDataProperty( + Object.create(null), + "value", + matcherConstructor, + ), + }, ); return { Matcher: matcherConstructor }; @@ -299,6 +315,25 @@ export const { }; })(); +/** + * Returns −0 if the provided argument is "-0"; returns a number + * representing the index if the provided argument is a canonical + * numeric index string; otherwise, returns undefined. + * + * There is no clamping of the numeric index, but note that numbers + * above 2^53 − 1 are not safe nor valid integer indices. + */ +export const canonicalNumericIndexString = ($) => { + if (typeof $ !== "string") { + return UNDEFINED; + } else if ($ === "-0") { + return -0; + } else { + const n = +$; + return $ === `${n}` ? n : UNDEFINED; + } +}; + export const { /** * Returns an iterator over the codepoints in the string representation @@ -375,19 +410,30 @@ export const { export const getCharacter = ($, pos) => { const codepoint = getCodepoint($, pos); return codepoint == null - ? undefined + ? UNDEFINED : stringFromCodepoints(codepoint); }; -/** - * Returns the code unit at the provided position in the string - * representation of the provided value according to the algorithm of - * `String::charAt`, except that out‐of‐bounds values return undefined - * in place of nan. - */ export const { + /** + * Returns the code unit at the provided position in the string + * representation of the provided value according to the algorithm of + * `String::charAt`, except that out‐of‐bounds values return undefined + * in place of nan. + */ getCodeUnit, + /** + * Returns a string created from the provided code units. + * + * ※ This is effectively an alias for `String.fromCharCode`, but + * with the same error behaviour as `String.fromCodePoint`. + * + * ☡ This function throws an error if provided with an argument which + * is not an integral number from 0 to FFFF₁₆ inclusive. + */ + stringFromCodeUnits, + /** * Returns the result of catenating the string representations of the * provided values, returning a new string according to the algorithm @@ -400,18 +446,45 @@ export const { */ stringCatenate, } = (() => { + const { fromCharCode } = String; const { charCodeAt, concat } = String.prototype; - const { isNaN: isNan } = Number; + const { + isInteger: isIntegralNumber, + isNaN: isNan, + } = Number; return { getCodeUnit: ($, n) => { const codeUnit = call(charCodeAt, $, [n]); - return isNan(codeUnit) ? undefined : codeUnit; + return isNan(codeUnit) ? UNDEFINED : codeUnit; }, - stringCatenate: defineOwnProperties( + stringCatenate: Object.defineProperties( (...args) => call(concat, "", args), { name: { value: "stringCatenate" }, length: { value: 2 } }, ), + stringFromCodeUnits: Object.defineProperties( + (...codeUnits) => { + for (let index = 0; index < codeUnits.length; ++index) { + // Iterate over each provided code unit and throw if it is + // out of range. + const nextCU = +codeUnits[index]; + if ( + !isIntegralNumber(nextCU) || nextCU < 0 || nextCU > 0xFFFF + ) { + // The code unit is not an integral number between 0 and + // 0xFFFF. + throw new RangeError( + `Piscēs: Code unit out of range: ${nextCU}.`, + ); + } else { + // The code unit is acceptable. + /* do nothing */ + } + } + return call(fromCharCode, UNDEFINED, codeUnits); + }, + { name: { value: "stringFromCodeUnits" }, length: { value: 1 } }, + ), }; })(); @@ -445,6 +518,34 @@ export const getLastSubstringIndex = createCallableFunction( { name: "getLastSubstringIndex" }, ); +/** Returns whether the provided value is an array index. */ +export const isArrayIndexString = ($) => { + const value = canonicalNumericIndexString($); + if (value !== UNDEFINED) { + // The provided value is a canonical numeric index string; return + // whether it is in range for array indices. + return sameValue(value, 0) || + value === toLength(value) && value > 0 && value < -1 >>> 0; + } else { + // The provided value is not a canonical numeric index string. + return false; + } +}; + +/** Returns whether the provided value is an integer index string. */ +export const isIntegerIndexString = ($) => { + const value = canonicalNumericIndexString($); + if (value !== UNDEFINED) { + // The provided value is a canonical numeric index string; return + // whether it is in range for integer indices. + return sameValue(value, 0) || + value === toLength(value) && value > 0; + } else { + // The provided value is not a canonical numeric index string. + return false; + } +}; + /** * Returns the result of joining the provided iterable. * @@ -458,7 +559,7 @@ export const join = (() => { call( arrayJoin, [...$], - [separator === undefined ? "," : `${separator}`], + [separator === UNDEFINED ? "," : `${separator}`], ); return join; })(); @@ -473,48 +574,6 @@ export const rawString = createArrowFunction(String.raw, { name: "rawString", }); -export const { - /** - * Returns a string created from the provided code units. - * - * ※ This is effectively an alias for `String.fromCharCode`, but - * with the same error behaviour as `String.fromCodePoint`. - * - * ☡ This function throws an error if provided with an argument which - * is not an integral number from 0 to FFFF₁₆ inclusive. - */ - stringFromCodeUnits, -} = (() => { - const { fromCharCode } = String; - const { isInteger: isIntegralNumber } = Number; - - return { - stringFromCodeUnits: defineOwnProperties( - (...codeUnits) => { - for (let index = 0; index < codeUnits.length; ++index) { - // Iterate over each provided code unit and throw if it is - // out of range. - const nextCU = +codeUnits[index]; - if ( - !isIntegralNumber(nextCU) || nextCU < 0 || nextCU > 0xFFFF - ) { - // The code unit is not an integral number between 0 and - // 0xFFFF. - throw new RangeError( - `Piscēs: Code unit out of range: ${nextCU}.`, - ); - } else { - // The code unit is acceptable. - /* do nothing */ - } - } - return call(fromCharCode, undefined, codeUnits); - }, - { name: { value: "stringFromCodeUnits" }, length: { value: 1 } }, - ), - }; -})(); - /** * Returns a string created from the provided codepoints. * @@ -529,19 +588,19 @@ export const stringFromCodepoints = createArrowFunction( ); /** - * Returns the result of splitting the provided value on A·S·C·I·I + * Returns the result of splitting the provided value on Ascii * whitespace. */ -export const splitOnASCIIWhitespace = ($) => - stringSplit(stripAndCollapseASCIIWhitespace($), " "); +export const splitOnAsciiWhitespace = ($) => + stringSplit(stripAndCollapseAsciiWhitespace($), " "); /** * Returns the result of splitting the provided value on commas, - * trimming A·S·C·I·I whitespace from the resulting tokens. + * trimming Ascii whitespace from the resulting tokens. */ export const splitOnCommas = ($) => stringSplit( - stripLeadingAndTrailingASCIIWhitespace( + stripLeadingAndTrailingAsciiWhitespace( stringReplaceAll( `${$}`, /[\n\r\t\f ]*,[\n\r\t\f ]*/gu, @@ -703,12 +762,12 @@ export const stringValue = createCallableFunction( ); /** - * Returns the result of stripping leading and trailing A·S·C·I·I - * whitespace from the provided value and collapsing other A·S·C·I·I + * Returns the result of stripping leading and trailing Ascii + * whitespace from the provided value and collapsing other Ascii * whitespace in the string representation of the provided value. */ -export const stripAndCollapseASCIIWhitespace = ($) => - stripLeadingAndTrailingASCIIWhitespace( +export const stripAndCollapseAsciiWhitespace = ($) => + stripLeadingAndTrailingAsciiWhitespace( stringReplaceAll( `${$}`, /[\n\r\t\f ]+/gu, @@ -717,10 +776,10 @@ export const stripAndCollapseASCIIWhitespace = ($) => ); /** - * Returns the result of stripping leading and trailing A·S·C·I·I + * Returns the result of stripping leading and trailing Ascii * whitespace from the string representation of the provided value. */ -export const stripLeadingAndTrailingASCIIWhitespace = ($) => +export const stripLeadingAndTrailingAsciiWhitespace = ($) => call(reExec, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u, [$])[1]; /**