UNPKG

locutus

Version:

Locutus other languages' standard libraries to JavaScript for fun and educational purposes

131 lines (130 loc) 4.96 kB
const toByte = (value) => ((Math.trunc(value) % 256) + 256) % 256; const toChar = (value) => String.fromCharCode(toByte(value)); function writeUInt16BE(value) { const n = Math.trunc(value) >>> 0; return toChar((n >>> 8) & 0xff) + toChar(n & 0xff); } function writeUInt16LE(value) { const n = Math.trunc(value) >>> 0; return toChar(n & 0xff) + toChar((n >>> 8) & 0xff); } function writeUInt32BE(value) { const n = Math.trunc(value) >>> 0; return toChar((n >>> 24) & 0xff) + toChar((n >>> 16) & 0xff) + toChar((n >>> 8) & 0xff) + toChar(n & 0xff); } function writeUInt32LE(value) { const n = Math.trunc(value) >>> 0; return toChar(n & 0xff) + toChar((n >>> 8) & 0xff) + toChar((n >>> 16) & 0xff) + toChar((n >>> 24) & 0xff); } function nibble(char) { const lower = char.toLowerCase(); if (lower >= '0' && lower <= '9') { return lower.charCodeAt(0) - 48; } if (lower >= 'a' && lower <= 'f') { return lower.charCodeAt(0) - 87; } return 0; } function packHex(hex, highFirst, nybbles) { const normalized = hex.replace(/[^0-9a-fA-F]/g, ''); const take = nybbles === -1 ? normalized.length : Math.max(0, nybbles); const input = normalized.slice(0, take); let out = ''; for (let i = 0; i < input.length; i += 2) { const a = nibble(input[i] ?? '0'); const b = nibble(input[i + 1] ?? '0'); const byte = highFirst ? (a << 4) | b : (b << 4) | a; out += toChar(byte); } if (input.length % 2 === 1) { const last = nibble(input.at(-1) ?? '0'); out += toChar(highFirst ? last << 4 : last); } return out; } export function pack(template, ...values) { // discuss at: https://locutus.io/perl/pack/ // original by: Kevin van Zonneveld (https://kvz.io) // note 1: Implements a practical subset: A, a, C, n, N, v, V, H, h with optional counts and *. // note 2: Output is a binary JavaScript string (char codes 0-255). // example 1: pack('A5', 'hi') // returns 1: 'hi ' // example 2: pack('C*', 72, 105, 33) // returns 2: 'Hi!' // example 3: pack('n', 258).split('').map((ch) => ch.charCodeAt(0)) // returns 3: [1, 2] const format = String(template).replace(/\s+/g, ''); let cursor = 0; let argIndex = 0; let out = ''; while (cursor < format.length) { const directive = format[cursor]; if (!directive) { break; } cursor += 1; let countToken = ''; while (cursor < format.length && /[0-9*]/.test(format[cursor] ?? '')) { countToken += format[cursor]; cursor += 1; } const count = countToken === '' ? 1 : countToken === '*' ? -1 : Number.parseInt(countToken, 10); const nextString = () => String(values[argIndex++] ?? ''); const nextNumber = () => Number(values[argIndex++] ?? 0); switch (directive) { case 'A': case 'a': { const value = nextString(); const width = count === -1 ? value.length : Math.max(0, count); const padded = directive === 'A' ? value.padEnd(width, ' ') : value.padEnd(width, '\0'); out += padded.slice(0, width); break; } case 'C': { const repeat = count === -1 ? values.length - argIndex : Math.max(0, count); for (let i = 0; i < repeat; i++) { out += toChar(nextNumber()); } break; } case 'n': { const repeat = count === -1 ? values.length - argIndex : Math.max(0, count); for (let i = 0; i < repeat; i++) { out += writeUInt16BE(nextNumber()); } break; } case 'v': { const repeat = count === -1 ? values.length - argIndex : Math.max(0, count); for (let i = 0; i < repeat; i++) { out += writeUInt16LE(nextNumber()); } break; } case 'N': { const repeat = count === -1 ? values.length - argIndex : Math.max(0, count); for (let i = 0; i < repeat; i++) { out += writeUInt32BE(nextNumber()); } break; } case 'V': { const repeat = count === -1 ? values.length - argIndex : Math.max(0, count); for (let i = 0; i < repeat; i++) { out += writeUInt32LE(nextNumber()); } break; } case 'H': case 'h': { const value = nextString(); out += packHex(value, directive === 'H', count); break; } default: break; } } return out; }