mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
for-of, spread, Array.from, yield* and [Symbol.iterator]() over a string walked a UTF-16 index. Each step mapped the index to a byte offset, built the character, and measured its UTF-16 length to advance. They now walk a byte cursor over the WTF-8 data: - The cpAtByte intrinsic reads the character that starts at the cursor. ASCII bytes take the one-character table inline; other characters call scr_str_cp_at_byte. byteLength advances the cursor by the character's byte count. A lone surrogate is its own three-byte character, so it stays a one-unit character, as in Node. - Spread and Array.from of a string call scr_str_code_points, which counts the characters, allocates the array once and fills it. - The one-character strings of U+0080..U+07FF are made immortal on first use and shared, as the ASCII ones are static. The cpAt intrinsic, which no lowering emits any more, is removed.
97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
// The string iterator: for-of, spread, Array.from, yield* and
|
|
// [Symbol.iterator]() walk code points. Astral characters come back whole,
|
|
// a lone surrogate is a one-unit character, and halves joined by
|
|
// concatenation form one character, as in Node.
|
|
function unitCodes(c: string): string {
|
|
const codes: string[] = [];
|
|
for (let k = 0; k < c.length; k++) codes.push(c.charCodeAt(k).toString(16));
|
|
return `${c.length}:${codes.join(".")}`;
|
|
}
|
|
const show = (chars: string[]): string => chars.map(unitCodes).join(" ");
|
|
|
|
const samples = [
|
|
"",
|
|
"abc",
|
|
"aé😀b",
|
|
"\u0000\u007f\u0080߿ࠀ",
|
|
"\u{10000}\u{10ffff}",
|
|
"é ñ",
|
|
"\ud800",
|
|
"\udc00",
|
|
"x\ud83d",
|
|
"\ude00x",
|
|
"\udc00\ud800",
|
|
"\ud83d" + "\ude00",
|
|
"a\ud83d" + "\ude00b\ud83d",
|
|
"日本語テキスト",
|
|
];
|
|
for (const s of samples) {
|
|
const looped: string[] = [];
|
|
for (const c of s) looped.push(c);
|
|
console.log(JSON.stringify(s), show(looped), show([...s]), show(Array.from(s)), [...s].length, s.length);
|
|
}
|
|
|
|
// Loop control, heads and bindings.
|
|
const text = "a😀b\ud800c";
|
|
let out = "";
|
|
outer: for (const c of text) {
|
|
if (c === "b") continue;
|
|
for (const d of "xy") {
|
|
if (d === "y" && c === "c") continue outer;
|
|
out += c + d;
|
|
}
|
|
if (c === "\ud800") out += "|";
|
|
}
|
|
console.log(out);
|
|
for (var v of "hé") out += v;
|
|
console.log(out);
|
|
let last = "";
|
|
for (last of "q😀") out += last.length;
|
|
console.log(out, last);
|
|
for (const [first] of ["😀z", "ab"].join("")) out += first;
|
|
console.log(out);
|
|
let source = "abc";
|
|
for (const c of source) {
|
|
source += c;
|
|
out += source.length;
|
|
}
|
|
console.log(out, source);
|
|
let n = 0;
|
|
for (const c of "stop😀here") {
|
|
if (c === "😀") break;
|
|
n += c.length;
|
|
}
|
|
console.log(n);
|
|
|
|
// Characters as values: lengths, Map keys and code points.
|
|
const counts = new Map<string, number>();
|
|
for (const c of "abracadabra😀😀é") counts.set(c, (counts.get(c) ?? 0) + 1);
|
|
console.log([...counts].map(([k, v]) => `${k}=${v}`).join(","));
|
|
let points = 0;
|
|
for (const c of "😀é\ud800a") points += c.codePointAt(0)!;
|
|
console.log(points);
|
|
|
|
// Generators and explicit iterators.
|
|
function* letters(s: string): Generator<string, void, undefined> {
|
|
yield* s;
|
|
}
|
|
console.log(show([...letters("x😀\udc00y")]));
|
|
const it = "o😀"[Symbol.iterator]();
|
|
let step = it.next();
|
|
while (!step.done) {
|
|
out += step.value.length;
|
|
step = it.next();
|
|
}
|
|
console.log(out);
|
|
|
|
// A long string built from mixed pieces.
|
|
let long = "";
|
|
for (let i = 0; i < 2000; i++) long += i % 3 === 0 ? "a" : i % 3 === 1 ? "é" : "😀";
|
|
let units = 0;
|
|
let chars = 0;
|
|
for (const c of long) {
|
|
units += c.length;
|
|
chars++;
|
|
}
|
|
console.log(units, chars, long.length, [...long].length);
|