Skip to content

Instantly share code, notes, and snippets.

@jamesliu96
Last active September 27, 2020 13:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesliu96/f38c5dc8ed9f3ceae9edacce488cd8a1 to your computer and use it in GitHub Desktop.
Save jamesliu96/f38c5dc8ed9f3ceae9edacce488cd8a1 to your computer and use it in GitHub Desktop.
var Mode;
(function (Mode) {
Mode[(Mode['Pad'] = 0)] = 'Pad';
Mode[(Mode['Bool'] = 1)] = 'Bool';
Mode[(Mode['Byte'] = 2)] = 'Byte';
Mode[(Mode['U_Byte'] = 3)] = 'U_Byte';
Mode[(Mode['Word'] = 4)] = 'Word';
Mode[(Mode['U_Word'] = 5)] = 'U_Word';
Mode[(Mode['DWord'] = 6)] = 'DWord';
Mode[(Mode['U_DWord'] = 7)] = 'U_DWord';
Mode[(Mode['F_DWord'] = 8)] = 'F_DWord';
Mode[(Mode['QWord'] = 9)] = 'QWord';
Mode[(Mode['U_QWord'] = 10)] = 'U_QWord';
Mode[(Mode['F_QWord'] = 11)] = 'F_QWord';
})(Mode || (Mode = {}));
const modeByteLengths = {
[Mode.Pad]: 1,
[Mode.Bool]: 1,
[Mode.Byte]: 1,
[Mode.U_Byte]: 1,
[Mode.Word]: 2,
[Mode.U_Word]: 2,
[Mode.DWord]: 4,
[Mode.U_DWord]: 4,
[Mode.F_DWord]: 4,
[Mode.QWord]: 8,
[Mode.U_QWord]: 8,
[Mode.F_QWord]: 8,
};
const types = {
x: Mode.Pad,
c: Mode.Byte,
b: Mode.Byte,
B: Mode.U_Byte,
'?': Mode.Bool,
h: Mode.Word,
H: Mode.U_Word,
i: Mode.DWord,
I: Mode.U_DWord,
l: Mode.DWord,
L: Mode.U_DWord,
q: Mode.QWord,
Q: Mode.U_QWord,
f: Mode.F_DWord,
d: Mode.F_QWord,
};
const _typez = Object.keys(types).join('');
const re = new RegExp(`^([<>]{0,1})([\\d${_typez}]*)$`);
const fre = new RegExp(`\\d*[${_typez}]`, 'g');
const wfre = new RegExp(`^(\\d*)([${_typez}])$`);
export default class Omelette {
constructor(format) {
this.littleEndian = false;
this.format = [];
const match = format.match(re);
if (match) {
const [_, ord, fmt] = match;
if (ord === '<') this.littleEndian = true;
const fmatch = fmt.match(fre);
if (fmatch) {
this.format = fmatch.map((w) => {
const wfmatch = w.match(wfre);
if (wfmatch) {
const [_, n, m] = wfmatch;
return [types[m], Number(n || 1)];
} else {
throw new Error(`invalid format \`${format}\``);
}
});
} else {
throw new Error(`invalid format \`${format}\``);
}
} else {
throw new Error(`invalid format \`${format}\``);
}
}
get byteLength() {
return this.format.reduce(
(acc, [mode, length]) => acc + modeByteLengths[mode] * length,
0
);
}
pack(...data) {
const buffer = new ArrayBuffer(this.byteLength);
const view = new DataView(buffer);
for (
let i = 0, idx = 0, offset = 0;
i < this.format.length && idx < data.length;
i++
) {
const [mode, length] = this.format[i];
const d = data[idx];
switch (mode) {
case Mode.Pad: {
this.repeat(length, () => {
view.setUint8(offset, 0);
offset += 1;
});
break;
}
case Mode.Bool: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint8(offset, v);
idx++;
offset += 1;
});
break;
}
case Mode.Byte: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setInt8(offset, v);
idx++;
offset += 1;
});
break;
}
case Mode.U_Byte: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint8(offset, v);
idx++;
offset += 1;
});
break;
}
case Mode.Word: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setInt16(offset, v, this.littleEndian);
idx++;
offset += 2;
});
break;
}
case Mode.U_Word: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint16(offset, v, this.littleEndian);
idx++;
offset += 2;
});
break;
}
case Mode.DWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setInt32(offset, v, this.littleEndian);
idx++;
offset += 4;
});
break;
}
case Mode.U_DWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint32(offset, v, this.littleEndian);
idx++;
offset += 4;
});
break;
}
case Mode.F_DWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setFloat32(offset, v, this.littleEndian);
idx++;
offset += 4;
});
break;
}
case Mode.QWord: {
this.repeat(length, () => {
const v = this.toBigNumber(d);
view.setBigInt64(offset, v, this.littleEndian);
idx++;
offset += 8;
});
break;
}
case Mode.U_QWord: {
this.repeat(length, () => {
const v = this.toBigNumber(d);
view.setBigUint64(offset, v, this.littleEndian);
idx++;
offset += 8;
});
break;
}
case Mode.F_QWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setFloat64(offset, v, this.littleEndian);
idx++;
offset += 8;
});
break;
}
}
}
return buffer;
}
repeat(times, fn) {
for (let idx = 0; idx < times; idx++) {
fn(idx);
}
}
toNumber(v) {
switch (typeof v) {
case 'string': {
return v.charCodeAt(0);
}
case 'number': {
return v;
}
case 'bigint': {
return Number(v);
}
case 'boolean': {
return v ? 1 : 0;
}
}
}
toBigNumber(v) {
switch (typeof v) {
case 'string': {
return BigInt(v.charCodeAt(0));
}
case 'number': {
return BigInt(v);
}
case 'bigint': {
return v;
}
case 'boolean': {
return v ? 1n : 0n;
}
}
}
}
enum Mode {
Pad, // 1
Bool, // 1
Byte, // 1
U_Byte, // 1
Word, // 2
U_Word, // 2
DWord, // 4
U_DWord, // 4
F_DWord, // 4
QWord, // 8
U_QWord, // 8
F_QWord, // 8
}
const modeByteLengths = {
[Mode.Pad]: 1,
[Mode.Bool]: 1,
[Mode.Byte]: 1,
[Mode.U_Byte]: 1,
[Mode.Word]: 2,
[Mode.U_Word]: 2,
[Mode.DWord]: 4,
[Mode.U_DWord]: 4,
[Mode.F_DWord]: 4,
[Mode.QWord]: 8,
[Mode.U_QWord]: 8,
[Mode.F_QWord]: 8,
};
const types: Record<string, Mode> = {
x: Mode.Pad,
c: Mode.Byte,
b: Mode.Byte,
B: Mode.U_Byte,
'?': Mode.Bool,
h: Mode.Word,
H: Mode.U_Word,
i: Mode.DWord,
I: Mode.U_DWord,
l: Mode.DWord,
L: Mode.U_DWord,
q: Mode.QWord,
Q: Mode.U_QWord,
f: Mode.F_DWord,
d: Mode.F_QWord,
};
const _typez = Object.keys(types).join('');
const re = new RegExp(`^([<>]{0,1})([\\d${_typez}]*)$`);
const fre = new RegExp(`\\d*[${_typez}]`, 'g');
const wfre = new RegExp(`^(\\d*)([${_typez}])$`);
export default class Omelette {
private littleEndian = false;
private format: [Mode, number][] = [];
private get byteLength() {
return this.format.reduce(
(acc, [mode, length]) => acc + modeByteLengths[mode] * length,
0
);
}
public constructor(format: string) {
const match = format.match(re);
if (match) {
const [_, ord, fmt] = match;
if (ord === '<') this.littleEndian = true;
const fmatch = fmt.match(fre);
if (fmatch) {
this.format = fmatch.map((w) => {
const wfmatch = w.match(wfre);
if (wfmatch) {
const [_, n, m] = wfmatch;
return [types[m], Number(n || 1)];
} else {
throw new Error(`invalid format \`${format}\``);
}
});
} else {
throw new Error(`invalid format \`${format}\``);
}
} else {
throw new Error(`invalid format \`${format}\``);
}
}
public pack(...data: (string | number | bigint | boolean)[]) {
const buffer = new ArrayBuffer(this.byteLength);
const view = new DataView(buffer);
for (
let i = 0, idx = 0, offset = 0;
i < this.format.length && idx < data.length;
i++
) {
const [mode, length] = this.format[i];
const d = data[idx];
switch (mode) {
case Mode.Pad: {
this.repeat(length, () => {
view.setUint8(offset, 0);
offset += 1;
});
break;
}
case Mode.Bool: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint8(offset, v);
idx++;
offset += 1;
});
break;
}
case Mode.Byte: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setInt8(offset, v);
idx++;
offset += 1;
});
break;
}
case Mode.U_Byte: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint8(offset, v);
idx++;
offset += 1;
});
break;
}
case Mode.Word: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setInt16(offset, v, this.littleEndian);
idx++;
offset += 2;
});
break;
}
case Mode.U_Word: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint16(offset, v, this.littleEndian);
idx++;
offset += 2;
});
break;
}
case Mode.DWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setInt32(offset, v, this.littleEndian);
idx++;
offset += 4;
});
break;
}
case Mode.U_DWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setUint32(offset, v, this.littleEndian);
idx++;
offset += 4;
});
break;
}
case Mode.F_DWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setFloat32(offset, v, this.littleEndian);
idx++;
offset += 4;
});
break;
}
case Mode.QWord: {
this.repeat(length, () => {
const v = this.toBigNumber(d);
view.setBigInt64(offset, v, this.littleEndian);
idx++;
offset += 8;
});
break;
}
case Mode.U_QWord: {
this.repeat(length, () => {
const v = this.toBigNumber(d);
view.setBigUint64(offset, v, this.littleEndian);
idx++;
offset += 8;
});
break;
}
case Mode.F_QWord: {
this.repeat(length, () => {
const v = this.toNumber(d);
view.setFloat64(offset, v, this.littleEndian);
idx++;
offset += 8;
});
break;
}
}
}
return buffer;
}
private repeat(times: number, fn: (...args: [number]) => void) {
for (let idx = 0; idx < times; idx++) {
fn(idx);
}
}
private toNumber(v: string | number | bigint | boolean): number {
switch (typeof v) {
case 'string': {
return v.charCodeAt(0);
}
case 'number': {
return v;
}
case 'bigint': {
return Number(v);
}
case 'boolean': {
return v ? 1 : 0;
}
}
}
private toBigNumber(v: string | number | bigint | boolean): bigint {
switch (typeof v) {
case 'string': {
return BigInt(v.charCodeAt(0));
}
case 'number': {
return BigInt(v);
}
case 'bigint': {
return v;
}
case 'boolean': {
return v ? 1n : 0n;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment