locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
131 lines (130 loc) • 4.96 kB
JavaScript
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;
}