diff --git a/src/vermiparous.ts b/src/vermiparous.ts new file mode 100644 index 0000000..b985a3d --- /dev/null +++ b/src/vermiparous.ts @@ -0,0 +1,103 @@ +const KEYWORDS = ["asset", "code"]; +const MIN_KW_LENGTH = Math.min(...KEYWORDS.map((x) => x.length)); +const SEMICOLON_CHARCODE = ";".charCodeAt(0); + +function isKeyword(buffer: number[]) { + const kw = String.fromCharCode(...buffer); + return KEYWORDS.includes(kw); +} + +function strToBytes(str: string): number[] { + return str.split("").map((x) => x.charCodeAt(0)); +} + +type EncAsset = { + name: string; + mime: string; + content: Uint8Array; +}; + +type DecAssic = { + tag: "code" | "asset"; + name: string; + mime: string; + content: Uint8Array; +}; + +export class Vermiparous { + private constructor() {} + static en(code: string, assets: EncAsset[]): Uint8Array { + const ret: number[] = []; + for (const asset of assets) { + ret.push(...strToBytes("asset")); + ret.push(...strToBytes(asset.name.length.toString())); + ret.push(...strToBytes(";")); + ret.push(...strToBytes(asset.mime.length.toString())); + ret.push(...strToBytes(";")); + ret.push(...strToBytes(asset.content.length.toString())); + ret.push(...strToBytes(";")); + ret.push(...strToBytes(asset.name)); + ret.push(...strToBytes(asset.mime)); + ret.push(...asset.content); + } + ret.push(...strToBytes("code")); + ret.push(...strToBytes("0;0;")); + ret.push(...strToBytes(code.length.toString())); + ret.push(...strToBytes(";")); + ret.push(...strToBytes(code)); + return new Uint8Array(ret); + } + + static de(bytes: Uint8Array): DecAssic[] { + const ret = []; + let buffer = []; + let idx = 0; + while (idx < bytes.length) { + buffer.push(bytes[idx]); + if (buffer.length < MIN_KW_LENGTH) { + ++idx; + continue; + } + if (!isKeyword(buffer)) { + ++idx; + continue; + } + const tag = buffer; + buffer = []; + ++idx; /* skip keyword last byte */ + const sizes = []; + for (let sizeIdx = 0; sizeIdx < 3; ++sizeIdx) { + while (idx < bytes.length) { + if (bytes[idx] === SEMICOLON_CHARCODE) { + ++idx; + break; + } + buffer.push(bytes[idx]); + ++idx; + } + sizes.push(parseInt(String.fromCharCode(...buffer))); + buffer = []; + } + const consoom = []; + for (let consoomIdx = 0; consoomIdx < 3; ++consoomIdx) { + let idekman = 0; + while (idekman < sizes[consoomIdx]) { + const byte = bytes[idx]; + buffer.push(byte); + idx++; + idekman++; + } + consoom.push(buffer); + buffer = []; + } + const [name, mime, content] = consoom; + ret.push({ + tag: String.fromCharCode(...tag) as DecAssic["tag"], + name: String.fromCharCode(...name), + mime: String.fromCharCode(...mime), + content: new Uint8Array(content), + }); + } + return ret; + } +}