Skip to content

Commit 0c5d518

Browse files
committed
fix(marshal)!: compare strings by codepoint
1 parent 3cf477a commit 0c5d518

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

packages/marshal/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export {
1717

1818
export {
1919
trivialComparator,
20+
compareByCodePoints,
2021
assertRankSorted,
2122
compareRank,
2223
isRankSorted,

packages/marshal/src/rankOrder.js

+41-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,46 @@ const { entries, fromEntries, setPrototypeOf, is } = Object;
4646
*/
4747
const sameValueZero = (x, y) => x === y || is(x, y);
4848

49+
/**
50+
* @param {any} left
51+
* @param {any} right
52+
* @returns {RankComparison}
53+
*/
4954
export const trivialComparator = (left, right) =>
5055
// eslint-disable-next-line no-nested-ternary, @endo/restrict-comparison-operands
5156
left < right ? -1 : left === right ? 0 : 1;
57+
harden(trivialComparator);
58+
59+
// Apparently eslint confused about whether the function can ever exit
60+
// without an explicit return.
61+
// eslint-disable-next-line jsdoc/require-returns-check
62+
/**
63+
* @param {string} left
64+
* @param {string} right
65+
* @returns {RankComparison}
66+
*/
67+
export const compareByCodePoints = (left, right) => {
68+
const leftIter = left[Symbol.iterator]();
69+
const rightIter = right[Symbol.iterator]();
70+
for (;;) {
71+
const { value: leftChar } = leftIter.next();
72+
const { value: rightChar } = rightIter.next();
73+
if (leftChar === undefined && rightChar === undefined) {
74+
return 0;
75+
} else if (leftChar === undefined) {
76+
// left is a prefix of right.
77+
return -1;
78+
} else if (rightChar === undefined) {
79+
// right is a prefix of left.
80+
return 1;
81+
}
82+
const leftCodepoint = /** @type {number} */ (leftChar.codePointAt(0));
83+
const rightCodepoint = /** @type {number} */ (rightChar.codePointAt(0));
84+
if (leftCodepoint < rightCodepoint) return -1;
85+
if (leftCodepoint > rightCodepoint) return 1;
86+
}
87+
};
88+
harden(compareByCodePoints);
5289

5390
/**
5491
* @typedef {Record<PassStyle, { index: number, cover: RankCover }>} PassStyleRanksRecord
@@ -140,8 +177,7 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => {
140177
return 0;
141178
}
142179
case 'boolean':
143-
case 'bigint':
144-
case 'string': {
180+
case 'bigint': {
145181
// Within each of these passStyles, the rank ordering agrees with
146182
// JavaScript's relational operators `<` and `>`.
147183
if (left < right) {
@@ -151,6 +187,9 @@ export const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => {
151187
return 1;
152188
}
153189
}
190+
case 'string': {
191+
return compareByCodePoints(left, right);
192+
}
154193
case 'symbol': {
155194
return comparator(
156195
nameForPassableSymbol(left),

0 commit comments

Comments
 (0)