tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

io-v2-utf8.ts (2097B)


      1 // Strict stateless UTF-8. No BOM stripping, replacement or normalization.
      2 // Source carries the copied <=3-byte suffix between requests.
      3 export class UTF8Error extends Error {}
      4 export function decodeUTF8(bytes: readonly number[], final: boolean): { text: string; carry: number[] } {
      5   if (bytes.length > 4096 || bytes.some(b => !Number.isInteger(b) || b < 0 || b > 255)) throw new UTF8Error('invalid byte chunk');
      6   const chars: string[] = [];
      7   const bad = (offset: number, why: string): never => { throw new UTF8Error(`${why} at byte ${offset}`); };
      8   for (let offset = 0; offset < bytes.length;) {
      9     const lead = bytes[offset]!;
     10     if (lead <= 0x7f) { chars.push(String.fromCodePoint(lead)); offset++; continue; }
     11     let count: number, low = 0x80, high = 0xbf, code: number;
     12     if (lead >= 0xc2 && lead <= 0xdf) { count = 2; code = lead & 0x1f; }
     13     else if (lead >= 0xe0 && lead <= 0xef) {
     14       count = 3; code = lead & 0x0f; if (lead === 0xe0) low = 0xa0; if (lead === 0xed) high = 0x9f;
     15     } else if (lead >= 0xf0 && lead <= 0xf4) {
     16       count = 4; code = lead & 7; if (lead === 0xf0) low = 0x90; if (lead === 0xf4) high = 0x8f;
     17     } else return bad(offset, 'invalid UTF-8 leading byte');
     18     const available = Math.min(count - 1, bytes.length - offset - 1);
     19     for (let i = 1; i <= available; i++) {
     20       const b = bytes[offset + i]!;
     21       if (b < (i === 1 ? low : 0x80) || b > (i === 1 ? high : 0xbf)) return bad(offset, 'invalid UTF-8 sequence');
     22       code = code * 64 + (b & 63);
     23     }
     24     if (available < count - 1) {
     25       if (final) return bad(offset, 'incomplete UTF-8 sequence');
     26       return { text: chars.join(''), carry: bytes.slice(offset) };
     27     }
     28     chars.push(String.fromCodePoint(code)); offset += count;
     29   }
     30   return { text: chars.join(''), carry: [] };
     31 }
     32 export function encodeUTF8(text: string): number[] {
     33   let count = 0;
     34   for (const char of text) {
     35     const scalar = char.codePointAt(0)!;
     36     if (++count > 1024 || scalar >= 0xd800 && scalar <= 0xdfff) throw new UTF8Error('invalid scalar chunk');
     37   }
     38   return [...new TextEncoder().encode(text)];
     39 }