Skip to content

Instantly share code, notes, and snippets.

@vbfox
Last active June 9, 2021 14:32
Show Gist options
  • Save vbfox/f4439ab4d216b432c707f20b90fb7693 to your computer and use it in GitHub Desktop.
Save vbfox/f4439ab4d216b432c707f20b90fb7693 to your computer and use it in GitHub Desktop.
Javascript/Typescript Rune iterators
/**
* Iterate a string runes. Same as iterating [...str]
*
* Note: This assume that the string is well-formed
*
* WARNING: This is useless, use the string iterator.
*/
export function* runeIterator(s: string) {
const len = s.length;
let highSurrogate: number | undefined;
for (let i = 0; i < len; i += 1) {
const code = s.charCodeAt(i);
if (highSurrogate !== undefined) {
yield String.fromCharCode(highSurrogate, code);
highSurrogate = undefined;
} else if (code >= 0xd800 && code <= 0xdbff) {
highSurrogate = code;
} else {
yield String.fromCharCode(code);
}
}
}
describe('runeIterator', () => {
test.each([
['', []],
['hello', ['h', 'e', 'l', 'l', 'o']],
['πŸ‘', ['πŸ‘']],
['πŸ›Έ πŸŒ‹ こん', ['πŸ›Έ', ' ', 'πŸŒ‹', ' ', 'こ', 'γ‚“']],
])('Expected result for %s: %O', (str, expected) => {
const iterator = runeIterator(str);
const parts = [...iterator];
expect(parts).toEqual(expected);
expect(parts).toEqual([...str]);
});
});
/**
* Iterate a string runes. Same as iterating [...str] but in reverse
*
* Note: This assume that the string is well-formed
*/
export function* reverseRuneIterator(s: string) {
let lowSurrogate: number | undefined;
for (let i = s.length - 1; i >= 0; i -= 1) {
const code = s.charCodeAt(i);
if (lowSurrogate !== undefined) {
yield String.fromCharCode(code, lowSurrogate);
lowSurrogate = undefined;
} else if (code >= 0xdc00 && code <= 0xdfff) {
lowSurrogate = code;
} else {
yield String.fromCharCode(code);
}
}
}
describe('reverseRuneIterator', () => {
test.each([
['', []],
['ab', ['b', 'a']],
['hello', ['o', 'l', 'l', 'e', 'h']],
['πŸ‘', ['πŸ‘']],
['πŸ›Έ πŸŒ‹ こん', ['γ‚“', 'こ', ' ', 'πŸŒ‹', ' ', 'πŸ›Έ']],
])('Expected result for %s: %O', (str, expected) => {
const iterator = reverseRuneIterator(str);
const parts = [...iterator];
expect(parts).toEqual(expected);
expect(parts).toEqual([...str].reverse());
});
});
function* codePointIterator(s) {
const len = s.length;
let highSurrogate;
for (let i = 0; i < len; i += 1) {
const code = s.charCodeAt(i);
if (highSurrogate !== undefined) {
yield ((highSurrogate - 0xd800) << 10) + (code - 0xdc00) + 0x10000;
highSurrogate = undefined;
} else if (code >= 0xd800 && code <= 0xdbff) {
highSurrogate = code;
} else {
yield code;
}
}
}
function* runeIteratorFromCodePoints(s) {
for (const c of codePointIterator(s)) {
yield String.fromCodePoint(c);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment