Install via yarn add locutus and import:
import { pack } from 'locutus/php/misc/pack'.
Or with CommonJS: const { pack } = require('locutus/php/misc/pack')
Use a bundler that supports tree-shaking so you only ship the functions you actually use.
Vite,
webpack,
Rollup, and
Parcel
all handle this. For server-side use this is less of a concern.
Examples
These examples are extracted from test cases that automatically verify our functions against their native counterparts.
exportfunctionpack(format: string, ...inputArgs: PackArgument[]): string { // discuss at: https://locutus.io/php/pack/ // original by: Tim de Koning (https://www.kingsquare.nl) // parts by: Jonas Raoni Soares Silva (https://www.jsfromhell.com) // bugfixed by: Tim de Koning (https://www.kingsquare.nl) // note 1: Float encoding by: Jonas Raoni Soares Silva // note 1: Home: https://www.kingsquare.nl/blog/12-12-2009/13507444 // note 1: Feedback: phpjs-pack@kingsquare.nl // note 1: "machine dependent byte order and size" aren't // note 1: applicable for JavaScript; pack works as on a 32bit, // note 1: little endian machine. // example 1: pack('nvc*', 0x1234, 0x5678, 65, 66) // returns 1: '\u00124xVAB' // example 2: pack('H4', '2345') // returns 2: '#E' // example 3: pack('H*', 'D5') // returns 3: 'Õ' // example 4: pack('d', -100.876) // returns 4: "\u0000\u0000\u0000\u0000\u00008YÀ"
let formatPointer = 0 let argumentPointer = 0 let result = '' letargument: string | number = '' let i = 0 letr: string[] = [] let instruction = '' letquantifier: number | '*' = 1 let word = '' let precisionBits = 0 let exponentBits = 0 let extraNullCount = 0
// vars used by float encoding let bias = 0 let minExp = 0 let maxExp = 0 let minUnnormExp = 0 let status = 0 let exp = 0 let len = 0 letbin: number[] = [] let signal = 0 let n = 0 let intPart = 0 let floatPart = 0 let lastBit = 0 let rounded = 0 let j = 0 let k = 0 let tmpResult = ''
// Now pack variables: 'quantifier' times 'instruction' switch (instruction) { case'a': case'A': { // NUL-padded string // SPACE-padded string if (typeofgetCurrentArgument() === 'undefined') { thrownewError('Warning: pack() Type ' + instruction + ': not enough arguments') } argument = String(getCurrentArgument()) const argString = String(argument) const count = quantifier === '*' ? argString.length : quantifier for (i = 0; i < count; i++) { if (typeof argString[i] === 'undefined') { if (instruction === 'a') { result += String.fromCharCode(0) } else { result += ' ' } } else { result += argString[i] } } argumentPointer++ break } case'h': case'H': { // Hex string, low nibble first // Hex string, high nibble first if (typeofgetCurrentArgument() === 'undefined') { thrownewError('Warning: pack() Type ' + instruction + ': not enough arguments') } argument = String(getCurrentArgument()) const argString = String(argument) const count = quantifier === '*' ? argString.length : quantifier if (count > argString.length) { thrownewError('Warning: pack() Type ' + instruction + ': not enough characters in string') }
for (i = 0; i < count; i += 2) { // Always get per 2 bytes... const first = argString[i] ?? '' const second = i + 1 >= count || typeof argString[i + 1] === 'undefined' ? '0' : (argString[i + 1] ?? '0') word = first + second // The fastest way to reverse? if (instruction === 'h') { word = (word[1] ?? '0') + (word[0] ?? '0') } result += String.fromCharCode(Number.parseInt(word, 16)) } argumentPointer++ break }
case'c': case'C': { // signed char // unsigned char // c and C is the same in pack const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { result += String.fromCharCode(getCurrentNumericArgument()) argumentPointer++ } break }
case's': case'S': case'v': { // signed short (always 16 bit, machine byte order) // unsigned short (always 16 bit, machine byte order) // s and S is the same in pack const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode(value & 0xff) result += String.fromCharCode((value >> 8) & 0xff) argumentPointer++ } break }
case'n': { // unsigned short (always 16 bit, big endian byte order) const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode((value >> 8) & 0xff) result += String.fromCharCode(value & 0xff) argumentPointer++ } break }
case'i': case'I': case'l': case'L': case'V': { // signed integer (machine dependent size and byte order) // unsigned integer (machine dependent size and byte order) // signed long (always 32 bit, machine byte order) // unsigned long (always 32 bit, machine byte order) // unsigned long (always 32 bit, little endian byte order) const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode(value & 0xff) result += String.fromCharCode((value >> 8) & 0xff) result += String.fromCharCode((value >> 16) & 0xff) result += String.fromCharCode((value >> 24) & 0xff) argumentPointer++ }
break } case'N': { // unsigned long (always 32 bit, big endian byte order) const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode((value >> 24) & 0xff) result += String.fromCharCode((value >> 16) & 0xff) result += String.fromCharCode((value >> 8) & 0xff) result += String.fromCharCode(value & 0xff) argumentPointer++ } break }
case'f': case'd': { // float (machine dependent size and representation) // double (machine dependent size and representation) // version based on IEEE754 precisionBits = 23 exponentBits = 8 if (instruction === 'd') { precisionBits = 52 exponentBits = 11 }
const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') } for (i = 0; i < count; i++) { argument = String(getCurrentArgument() ?? '') bias = Math.pow(2, exponentBits - 1) - 1 minExp = -bias + 1 maxExp = bias minUnnormExp = minExp - precisionBits n = Number.parseFloat(String(argument)) status = Number.isNaN(n) || n === -Infinity || n === Infinity ? n : 0 exp = 0 len = 2 * bias + 1 + precisionBits + 3 bin = newArray<number>(len) signal = (n = status !== 0 ? 0 : n) < 0 ? 1 : 0 n = Math.abs(n) intPart = Math.floor(n) floatPart = n - intPart
case'x': { // NUL byte if (quantifier === '*') { thrownewError("Warning: pack(): Type x: '*' ignored") } for (i = 0; i < quantifier; i++) { result += String.fromCharCode(0) } break }
case'X': { // Back up one byte if (quantifier === '*') { thrownewError("Warning: pack(): Type X: '*' ignored") } for (i = 0; i < quantifier; i++) { if (result.length === 0) { thrownewError('Warning: pack(): Type X:' + ' outside of string') } result = result.slice(0, -1) } break }
case'@': { // NUL-fill to absolute position if (quantifier === '*') { thrownewError("Warning: pack(): Type X: '*' ignored") } if (quantifier > result.length) { extraNullCount = quantifier - result.length for (i = 0; i < extraNullCount; i++) { result += String.fromCharCode(0) } } if (quantifier < result.length) { result = result.slice(0, quantifier) } break }
exportfunctionpack(format, ...inputArgs) { // discuss at: https://locutus.io/php/pack/ // original by: Tim de Koning (https://www.kingsquare.nl) // parts by: Jonas Raoni Soares Silva (https://www.jsfromhell.com) // bugfixed by: Tim de Koning (https://www.kingsquare.nl) // note 1: Float encoding by: Jonas Raoni Soares Silva // note 1: Home: https://www.kingsquare.nl/blog/12-12-2009/13507444 // note 1: Feedback: phpjs-pack@kingsquare.nl // note 1: "machine dependent byte order and size" aren't // note 1: applicable for JavaScript; pack works as on a 32bit, // note 1: little endian machine. // example 1: pack('nvc*', 0x1234, 0x5678, 65, 66) // returns 1: '\u00124xVAB' // example 2: pack('H4', '2345') // returns 2: '#E' // example 3: pack('H*', 'D5') // returns 3: 'Õ' // example 4: pack('d', -100.876) // returns 4: "\u0000\u0000\u0000\u0000\u00008YÀ"
let formatPointer = 0 let argumentPointer = 0 let result = '' let argument = '' let i = 0 let r = [] let instruction = '' let quantifier = 1 let word = '' let precisionBits = 0 let exponentBits = 0 let extraNullCount = 0
// vars used by float encoding let bias = 0 let minExp = 0 let maxExp = 0 let minUnnormExp = 0 let status = 0 let exp = 0 let len = 0 let bin = [] let signal = 0 let n = 0 let intPart = 0 let floatPart = 0 let lastBit = 0 let rounded = 0 let j = 0 let k = 0 let tmpResult = ''
// Now pack variables: 'quantifier' times 'instruction' switch (instruction) { case'a': case'A': { // NUL-padded string // SPACE-padded string if (typeofgetCurrentArgument() === 'undefined') { thrownewError('Warning: pack() Type ' + instruction + ': not enough arguments') } argument = String(getCurrentArgument()) const argString = String(argument) const count = quantifier === '*' ? argString.length : quantifier for (i = 0; i < count; i++) { if (typeof argString[i] === 'undefined') { if (instruction === 'a') { result += String.fromCharCode(0) } else { result += ' ' } } else { result += argString[i] } } argumentPointer++ break } case'h': case'H': { // Hex string, low nibble first // Hex string, high nibble first if (typeofgetCurrentArgument() === 'undefined') { thrownewError('Warning: pack() Type ' + instruction + ': not enough arguments') } argument = String(getCurrentArgument()) const argString = String(argument) const count = quantifier === '*' ? argString.length : quantifier if (count > argString.length) { thrownewError('Warning: pack() Type ' + instruction + ': not enough characters in string') }
for (i = 0; i < count; i += 2) { // Always get per 2 bytes... const first = argString[i] ?? '' const second = i + 1 >= count || typeof argString[i + 1] === 'undefined' ? '0' : (argString[i + 1] ?? '0') word = first + second // The fastest way to reverse? if (instruction === 'h') { word = (word[1] ?? '0') + (word[0] ?? '0') } result += String.fromCharCode(Number.parseInt(word, 16)) } argumentPointer++ break }
case'c': case'C': { // signed char // unsigned char // c and C is the same in pack const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { result += String.fromCharCode(getCurrentNumericArgument()) argumentPointer++ } break }
case's': case'S': case'v': { // signed short (always 16 bit, machine byte order) // unsigned short (always 16 bit, machine byte order) // s and S is the same in pack const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode(value & 0xff) result += String.fromCharCode((value >> 8) & 0xff) argumentPointer++ } break }
case'n': { // unsigned short (always 16 bit, big endian byte order) const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode((value >> 8) & 0xff) result += String.fromCharCode(value & 0xff) argumentPointer++ } break }
case'i': case'I': case'l': case'L': case'V': { // signed integer (machine dependent size and byte order) // unsigned integer (machine dependent size and byte order) // signed long (always 32 bit, machine byte order) // unsigned long (always 32 bit, machine byte order) // unsigned long (always 32 bit, little endian byte order) const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode(value & 0xff) result += String.fromCharCode((value >> 8) & 0xff) result += String.fromCharCode((value >> 16) & 0xff) result += String.fromCharCode((value >> 24) & 0xff) argumentPointer++ }
break } case'N': { // unsigned long (always 32 bit, big endian byte order) const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') }
for (i = 0; i < count; i++) { const value = getCurrentNumericArgument() result += String.fromCharCode((value >> 24) & 0xff) result += String.fromCharCode((value >> 16) & 0xff) result += String.fromCharCode((value >> 8) & 0xff) result += String.fromCharCode(value & 0xff) argumentPointer++ } break }
case'f': case'd': { // float (machine dependent size and representation) // double (machine dependent size and representation) // version based on IEEE754 precisionBits = 23 exponentBits = 8 if (instruction === 'd') { precisionBits = 52 exponentBits = 11 }
const count = quantifier === '*' ? getRemainingArgumentCount() : quantifier if (count > getRemainingArgumentCount()) { thrownewError('Warning: pack() Type ' + instruction + ': too few arguments') } for (i = 0; i < count; i++) { argument = String(getCurrentArgument() ?? '') bias = Math.pow(2, exponentBits - 1) - 1 minExp = -bias + 1 maxExp = bias minUnnormExp = minExp - precisionBits n = Number.parseFloat(String(argument)) status = Number.isNaN(n) || n === -Infinity || n === Infinity ? n : 0 exp = 0 len = 2 * bias + 1 + precisionBits + 3 bin = newArray(len) signal = (n = status !== 0 ? 0 : n) < 0 ? 1 : 0 n = Math.abs(n) intPart = Math.floor(n) floatPart = n - intPart
case'x': { // NUL byte if (quantifier === '*') { thrownewError("Warning: pack(): Type x: '*' ignored") } for (i = 0; i < quantifier; i++) { result += String.fromCharCode(0) } break }
case'X': { // Back up one byte if (quantifier === '*') { thrownewError("Warning: pack(): Type X: '*' ignored") } for (i = 0; i < quantifier; i++) { if (result.length === 0) { thrownewError('Warning: pack(): Type X:' + ' outside of string') } result = result.slice(0, -1) } break }
case'@': { // NUL-fill to absolute position if (quantifier === '*') { thrownewError("Warning: pack(): Type X: '*' ignored") } if (quantifier > result.length) { extraNullCount = quantifier - result.length for (i = 0; i < extraNullCount; i++) { result += String.fromCharCode(0) } } if (quantifier < result.length) { result = result.slice(0, quantifier) } break }
Locutus is a community effort following
The McDonald's Theory:
we ship first iterations, hoping others will improve them.
If you see something that could be better, we'd love your contribution.