tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

io-utf8.js (2387B)


      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 }
      5 export function decodeUTF8(bytes, final) {
      6     if (bytes.length > 4096 || bytes.some(b => !Number.isInteger(b) || b < 0 || b > 255))
      7         throw new UTF8Error('invalid byte chunk');
      8     const chars = [];
      9     const bad = (offset, why) => { throw new UTF8Error(`${why} at byte ${offset}`); };
     10     for (let offset = 0; offset < bytes.length;) {
     11         const lead = bytes[offset];
     12         if (lead <= 0x7f) {
     13             chars.push(String.fromCodePoint(lead));
     14             offset++;
     15             continue;
     16         }
     17         let count, low = 0x80, high = 0xbf, code;
     18         if (lead >= 0xc2 && lead <= 0xdf) {
     19             count = 2;
     20             code = lead & 0x1f;
     21         }
     22         else if (lead >= 0xe0 && lead <= 0xef) {
     23             count = 3;
     24             code = lead & 0x0f;
     25             if (lead === 0xe0)
     26                 low = 0xa0;
     27             if (lead === 0xed)
     28                 high = 0x9f;
     29         }
     30         else if (lead >= 0xf0 && lead <= 0xf4) {
     31             count = 4;
     32             code = lead & 7;
     33             if (lead === 0xf0)
     34                 low = 0x90;
     35             if (lead === 0xf4)
     36                 high = 0x8f;
     37         }
     38         else
     39             return bad(offset, 'invalid UTF-8 leading byte');
     40         const available = Math.min(count - 1, bytes.length - offset - 1);
     41         for (let i = 1; i <= available; i++) {
     42             const b = bytes[offset + i];
     43             if (b < (i === 1 ? low : 0x80) || b > (i === 1 ? high : 0xbf))
     44                 return bad(offset, 'invalid UTF-8 sequence');
     45             code = code * 64 + (b & 63);
     46         }
     47         if (available < count - 1) {
     48             if (final)
     49                 return bad(offset, 'incomplete UTF-8 sequence');
     50             return { text: chars.join(''), carry: bytes.slice(offset) };
     51         }
     52         chars.push(String.fromCodePoint(code));
     53         offset += count;
     54     }
     55     return { text: chars.join(''), carry: [] };
     56 }
     57 export function encodeUTF8(text) {
     58     let count = 0;
     59     for (const char of text) {
     60         const scalar = char.codePointAt(0);
     61         if (++count > 1024 || scalar >= 0xd800 && scalar <= 0xdfff)
     62             throw new UTF8Error('invalid scalar chunk');
     63     }
     64     return [...new TextEncoder().encode(text)];
     65 }