import { Buffer } from "./buffer.ts";import { normalizeEncoding as castEncoding, notImplemented } from "./_utils.ts";
enum NotImplemented { "ascii", "latin1", "utf16le",}
function normalizeEncoding(enc?: string): string { const encoding = castEncoding(enc ?? null); if (encoding && encoding in NotImplemented) notImplemented(encoding); if (!encoding && typeof enc === "string" && enc.toLowerCase() !== "raw") { throw new Error(`Unknown encoding: ${enc}`); } return String(encoding);}function utf8CheckByte(byte: number): number { if (byte <= 0x7f) return 0; else if (byte >> 5 === 0x06) return 2; else if (byte >> 4 === 0x0e) return 3; else if (byte >> 3 === 0x1e) return 4; return byte >> 6 === 0x02 ? -1 : -2;}
function utf8CheckIncomplete( self: StringDecoderBase, buf: Buffer, i: number,): number { let j = buf.length - 1; if (j < i) return 0; let nb = utf8CheckByte(buf[j]); if (nb >= 0) { if (nb > 0) self.lastNeed = nb - 1; return nb; } if (--j < i || nb === -2) return 0; nb = utf8CheckByte(buf[j]); if (nb >= 0) { if (nb > 0) self.lastNeed = nb - 2; return nb; } if (--j < i || nb === -2) return 0; nb = utf8CheckByte(buf[j]); if (nb >= 0) { if (nb > 0) { if (nb === 2) nb = 0; else self.lastNeed = nb - 3; } return nb; } return 0;}
function utf8CheckExtraBytes( self: StringDecoderBase, buf: Buffer,): string | undefined { if ((buf[0] & 0xc0) !== 0x80) { self.lastNeed = 0; return "\ufffd"; } if (self.lastNeed > 1 && buf.length > 1) { if ((buf[1] & 0xc0) !== 0x80) { self.lastNeed = 1; return "\ufffd"; } if (self.lastNeed > 2 && buf.length > 2) { if ((buf[2] & 0xc0) !== 0x80) { self.lastNeed = 2; return "\ufffd"; } } }}
function utf8FillLastComplete( this: StringDecoderBase, buf: Buffer,): string | undefined { const p = this.lastTotal - this.lastNeed; const r = utf8CheckExtraBytes(this, buf); if (r !== undefined) return r; if (this.lastNeed <= buf.length) { buf.copy(this.lastChar, p, 0, this.lastNeed); return this.lastChar.toString(this.encoding, 0, this.lastTotal); } buf.copy(this.lastChar, p, 0, buf.length); this.lastNeed -= buf.length;}
function utf8FillLastIncomplete( this: StringDecoderBase, buf: Buffer,): string | undefined { if (this.lastNeed <= buf.length) { buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed); return this.lastChar.toString(this.encoding, 0, this.lastTotal); } buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length); this.lastNeed -= buf.length;}
function utf8Text(this: StringDecoderBase, buf: Buffer, i: number): string { const total = utf8CheckIncomplete(this, buf, i); if (!this.lastNeed) return buf.toString("utf8", i); this.lastTotal = total; const end = buf.length - (total - this.lastNeed); buf.copy(this.lastChar, 0, end); return buf.toString("utf8", i, end);}
function utf8End(this: Utf8Decoder, buf?: Buffer): string { const r = buf && buf.length ? this.write(buf) : ""; if (this.lastNeed) return r + "\ufffd"; return r;}
function utf8Write( this: Utf8Decoder | Base64Decoder, buf: Buffer | string,): string { if (typeof buf === "string") { return buf; } if (buf.length === 0) return ""; let r; let i; if (this.lastNeed) { r = this.fillLast(buf); if (r === undefined) return ""; i = this.lastNeed; this.lastNeed = 0; } else { i = 0; } if (i < buf.length) return r ? r + this.text(buf, i) : this.text(buf, i); return r || "";}
function base64Text(this: StringDecoderBase, buf: Buffer, i: number): string { const n = (buf.length - i) % 3; if (n === 0) return buf.toString("base64", i); this.lastNeed = 3 - n; this.lastTotal = 3; if (n === 1) { this.lastChar[0] = buf[buf.length - 1]; } else { this.lastChar[0] = buf[buf.length - 2]; this.lastChar[1] = buf[buf.length - 1]; } return buf.toString("base64", i, buf.length - n);}
function base64End(this: Base64Decoder, buf?: Buffer): string { const r = buf && buf.length ? this.write(buf) : ""; if (this.lastNeed) { return r + this.lastChar.toString("base64", 0, 3 - this.lastNeed); } return r;}
function simpleWrite( this: StringDecoderBase, buf: Buffer | string,): string { if (typeof buf === "string") { return buf; } return buf.toString(this.encoding);}
function simpleEnd(this: GenericDecoder, buf?: Buffer): string { return buf && buf.length ? this.write(buf) : "";}
class StringDecoderBase { public lastChar: Buffer; public lastNeed = 0; public lastTotal = 0; constructor(public encoding: string, nb: number) { this.lastChar = Buffer.allocUnsafe(nb); }}
class Base64Decoder extends StringDecoderBase { public end = base64End; public fillLast = utf8FillLastIncomplete; public text = base64Text; public write = utf8Write;
constructor(encoding?: string) { super(normalizeEncoding(encoding), 3); }}
class GenericDecoder extends StringDecoderBase { public end = simpleEnd; public fillLast = undefined; public text = utf8Text; public write = simpleWrite;
constructor(encoding?: string) { super(normalizeEncoding(encoding), 4); }}
class Utf8Decoder extends StringDecoderBase { public end = utf8End; public fillLast = utf8FillLastComplete; public text = utf8Text; public write = utf8Write;
constructor(encoding?: string) { super(normalizeEncoding(encoding), 4); }}
export class StringDecoder { public encoding: string; public end: (buf?: Buffer) => string; public fillLast: ((buf: Buffer) => string | undefined) | undefined; public lastChar: Buffer; public lastNeed: number; public lastTotal: number; public text: (buf: Buffer, n: number) => string; public write: (buf: Buffer) => string;
constructor(encoding?: string) { let decoder; switch (encoding) { case "utf8": decoder = new Utf8Decoder(encoding); break; case "base64": decoder = new Base64Decoder(encoding); break; default: decoder = new GenericDecoder(encoding); } this.encoding = decoder.encoding; this.end = decoder.end; this.fillLast = decoder.fillLast; this.lastChar = decoder.lastChar; this.lastNeed = decoder.lastNeed; this.lastTotal = decoder.lastTotal; this.text = decoder.text; this.write = decoder.write; }}
export default { StringDecoder };