lots of small cleanup
This commit is contained in:
parent
ec035e9a5d
commit
362fda5b4b
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* TODO: bigint, large decimals. For right now, only use numbers that fit in
|
||||||
|
* JavaScript number.
|
||||||
|
*/
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
export declare const Types: {
|
export declare const Types: {
|
||||||
readonly BERT_START: 131;
|
readonly BERT_START: 131;
|
||||||
|
@ -22,6 +26,23 @@ export declare const Lang: {
|
||||||
readonly ELIXIR: 0;
|
readonly ELIXIR: 0;
|
||||||
readonly ERLANG: 1;
|
readonly ERLANG: 1;
|
||||||
};
|
};
|
||||||
|
interface Partial<Type> {
|
||||||
|
value: Type;
|
||||||
|
rest: Buffer;
|
||||||
|
}
|
||||||
|
export declare class Atom {
|
||||||
|
readonly value: string;
|
||||||
|
private constructor();
|
||||||
|
private static atoms;
|
||||||
|
static toAtom: (value: string) => Atom;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* WIP WIP WIP
|
||||||
|
*/
|
||||||
|
export declare class Tuple {
|
||||||
|
readonly values: any[];
|
||||||
|
constructor(values: any[]);
|
||||||
|
}
|
||||||
export declare class Bert {
|
export declare class Bert {
|
||||||
#private;
|
#private;
|
||||||
private allBinariesAsString;
|
private allBinariesAsString;
|
||||||
|
@ -147,150 +168,21 @@ export declare class Bert {
|
||||||
encode_tuple: (obj: Buffer, buffer: Buffer) => Buffer;
|
encode_tuple: (obj: Buffer, buffer: Buffer) => Buffer;
|
||||||
encode_array: (obj: any[], buffer: Buffer) => Buffer;
|
encode_array: (obj: any[], buffer: Buffer) => Buffer;
|
||||||
encode_map: (obj: Record<string, any>, buffer: Buffer) => Buffer;
|
encode_map: (obj: Record<string, any>, buffer: Buffer) => Buffer;
|
||||||
decode_atom: (buffer: Buffer, count: 1 | 2 | 4) => {
|
decode_atom: (buffer: Buffer, count: 1 | 2 | 4) => Partial<any>;
|
||||||
value: any;
|
decode_binary: (buffer: Buffer) => Partial<string | Buffer>;
|
||||||
rest: Buffer;
|
decode_integer: (buffer: Buffer, count: 1 | 2 | 4, unsigned?: boolean) => Partial<number>;
|
||||||
};
|
|
||||||
decode_binary: (buffer: Buffer) => {
|
|
||||||
value: string | Buffer;
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
decode_integer: (buffer: Buffer, count: 1 | 2 | 4, unsigned?: boolean) => {
|
|
||||||
value: number;
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
decode_big: (buffer: Buffer, count: 1 | 2 | 4) => {
|
decode_big: (buffer: Buffer, count: 1 | 2 | 4) => {
|
||||||
value: number;
|
value: number;
|
||||||
rest: Buffer;
|
rest: Buffer;
|
||||||
};
|
};
|
||||||
decode_float: (buffer: Buffer) => {
|
decode_float: (buffer: Buffer) => Partial<number>;
|
||||||
value: number;
|
decode_new_float: (buffer: Buffer) => Partial<number>;
|
||||||
rest: Buffer;
|
decode_string: (buffer: Buffer) => Partial<string>;
|
||||||
};
|
decode_list: (buffer: Buffer) => Partial<any[]>;
|
||||||
decode_new_float: (buffer: Buffer) => {
|
decode_map: (buffer: Buffer) => Partial<Record<string, any>>;
|
||||||
value: number;
|
decode_tuple: (buffer: Buffer, count: 1 | 2 | 4) => Partial<any>;
|
||||||
rest: Buffer;
|
decode_nil: (buffer: Buffer) => Partial<[]>;
|
||||||
};
|
|
||||||
decode_string: (buffer: Buffer) => {
|
|
||||||
value: string;
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
decode_list: (buffer: Buffer) => {
|
|
||||||
value: any[];
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
decode_map: (buffer: Buffer) => {
|
|
||||||
value: Record<string, any>;
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
decode_tuple: (buffer: Buffer, count: 1 | 2 | 4) => {
|
|
||||||
value: {
|
|
||||||
type: string;
|
|
||||||
length: number;
|
|
||||||
value: any[];
|
|
||||||
toString: () => string;
|
|
||||||
toLocaleString(): string;
|
|
||||||
pop(): any;
|
|
||||||
push(...items: any[]): number;
|
|
||||||
concat(...items: ConcatArray<any>[]): any[];
|
|
||||||
concat(...items: any[]): any[];
|
|
||||||
join(separator?: string | undefined): string;
|
|
||||||
reverse(): any[];
|
|
||||||
shift(): any;
|
|
||||||
slice(start?: number | undefined, end?: number | undefined): any[];
|
|
||||||
sort(compareFn?: ((a: any, b: any) => number) | undefined): any[];
|
|
||||||
splice(start: number, deleteCount?: number | undefined): any[];
|
|
||||||
splice(start: number, deleteCount: number, ...items: any[]): any[];
|
|
||||||
unshift(...items: any[]): number;
|
|
||||||
indexOf(searchElement: any, fromIndex?: number | undefined): number;
|
|
||||||
lastIndexOf(searchElement: any, fromIndex?: number | undefined): number;
|
|
||||||
every<S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): this is S[];
|
|
||||||
every(predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): boolean;
|
|
||||||
some(predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): boolean;
|
|
||||||
forEach(callbackfn: (value: any, index: number, array: any[]) => void, thisArg?: any): void;
|
|
||||||
map<U>(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any): U[];
|
|
||||||
filter<S_1 extends any>(predicate: (value: any, index: number, array: any[]) => value is S_1, thisArg?: any): S_1[];
|
|
||||||
filter(predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[];
|
|
||||||
reduce(callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any): any;
|
|
||||||
reduce(callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue: any): any;
|
|
||||||
reduce<U_1>(callbackfn: (previousValue: U_1, currentValue: any, currentIndex: number, array: any[]) => U_1, initialValue: U_1): U_1;
|
|
||||||
reduceRight(callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any): any;
|
|
||||||
reduceRight(callbackfn: (previousValue: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue: any): any;
|
|
||||||
reduceRight<U_2>(callbackfn: (previousValue: U_2, currentValue: any, currentIndex: number, array: any[]) => U_2, initialValue: U_2): U_2;
|
|
||||||
find<S_2 extends any>(predicate: (value: any, index: number, obj: any[]) => value is S_2, thisArg?: any): S_2 | undefined;
|
|
||||||
find(predicate: (value: any, index: number, obj: any[]) => unknown, thisArg?: any): any;
|
|
||||||
findIndex(predicate: (value: any, index: number, obj: any[]) => unknown, thisArg?: any): number;
|
|
||||||
fill(value: any, start?: number | undefined, end?: number | undefined): any[];
|
|
||||||
copyWithin(target: number, start: number, end?: number | undefined): any[];
|
|
||||||
entries(): IterableIterator<[number, any]>;
|
|
||||||
keys(): IterableIterator<number>;
|
|
||||||
values(): IterableIterator<any>;
|
|
||||||
includes(searchElement: any, fromIndex?: number | undefined): boolean;
|
|
||||||
flatMap<U_3, This = undefined>(callback: (this: This, value: any, index: number, array: any[]) => U_3 | readonly U_3[], thisArg?: This | undefined): U_3[];
|
|
||||||
flat<A, D extends number = 1>(this: A, depth?: D | undefined): FlatArray<A, D>[];
|
|
||||||
at(index: number): any;
|
|
||||||
findLast<S_3 extends any>(predicate: (value: any, index: number, array: any[]) => value is S_3, thisArg?: any): S_3 | undefined;
|
|
||||||
findLast(predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any;
|
|
||||||
findLastIndex(predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): number;
|
|
||||||
toReversed(): any[];
|
|
||||||
toSorted(compareFn?: ((a: any, b: any) => number) | undefined): any[];
|
|
||||||
toSpliced(start: number, deleteCount: number, ...items: any[]): any[];
|
|
||||||
toSpliced(start: number, deleteCount?: number | undefined): any[];
|
|
||||||
with(index: number, value: any): any[];
|
|
||||||
[Symbol.iterator](): IterableIterator<any>;
|
|
||||||
[Symbol.unscopables]: {
|
|
||||||
[x: number]: boolean | undefined;
|
|
||||||
length?: boolean | undefined;
|
|
||||||
toString?: boolean | undefined;
|
|
||||||
toLocaleString?: boolean | undefined;
|
|
||||||
pop?: boolean | undefined;
|
|
||||||
push?: boolean | undefined;
|
|
||||||
concat?: boolean | undefined;
|
|
||||||
join?: boolean | undefined;
|
|
||||||
reverse?: boolean | undefined;
|
|
||||||
shift?: boolean | undefined;
|
|
||||||
slice?: boolean | undefined;
|
|
||||||
sort?: boolean | undefined;
|
|
||||||
splice?: boolean | undefined;
|
|
||||||
unshift?: boolean | undefined;
|
|
||||||
indexOf?: boolean | undefined;
|
|
||||||
lastIndexOf?: boolean | undefined;
|
|
||||||
every?: boolean | undefined;
|
|
||||||
some?: boolean | undefined;
|
|
||||||
forEach?: boolean | undefined;
|
|
||||||
map?: boolean | undefined;
|
|
||||||
filter?: boolean | undefined;
|
|
||||||
reduce?: boolean | undefined;
|
|
||||||
reduceRight?: boolean | undefined;
|
|
||||||
find?: boolean | undefined;
|
|
||||||
findIndex?: boolean | undefined;
|
|
||||||
fill?: boolean | undefined;
|
|
||||||
copyWithin?: boolean | undefined;
|
|
||||||
entries?: boolean | undefined;
|
|
||||||
keys?: boolean | undefined;
|
|
||||||
values?: boolean | undefined;
|
|
||||||
includes?: boolean | undefined;
|
|
||||||
flatMap?: boolean | undefined;
|
|
||||||
flat?: boolean | undefined;
|
|
||||||
at?: boolean | undefined;
|
|
||||||
findLast?: boolean | undefined;
|
|
||||||
findLastIndex?: boolean | undefined;
|
|
||||||
toReversed?: boolean | undefined;
|
|
||||||
toSorted?: boolean | undefined;
|
|
||||||
toSpliced?: boolean | undefined;
|
|
||||||
with?: boolean | undefined;
|
|
||||||
[Symbol.iterator]?: boolean | undefined;
|
|
||||||
readonly [Symbol.unscopables]?: boolean | undefined;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
decode_nil: (buffer: Buffer) => {
|
|
||||||
value: never[];
|
|
||||||
rest: Buffer;
|
|
||||||
};
|
|
||||||
bytesToInt: (buffer: Buffer, length: 1 | 2 | 4, unsigned: boolean) => number;
|
bytesToInt: (buffer: Buffer, length: 1 | 2 | 4, unsigned: boolean) => number;
|
||||||
binary_to_list: (str: string) => string[];
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Convert object to atom.
|
* Convert object to atom.
|
||||||
|
@ -404,3 +296,4 @@ export declare const toTuple: (arr: any[]) => {
|
||||||
readonly [Symbol.unscopables]?: boolean | undefined;
|
readonly [Symbol.unscopables]?: boolean | undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export {};
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
/**
|
||||||
|
* TODO: bigint, large decimals. For right now, only use numbers that fit in
|
||||||
|
* JavaScript number.
|
||||||
|
*/
|
||||||
export const Types = {
|
export const Types = {
|
||||||
BERT_START: 131,
|
BERT_START: 131,
|
||||||
SMALL_ATOM: 119,
|
SMALL_ATOM: 119,
|
||||||
|
@ -21,7 +25,52 @@ export const Lang = {
|
||||||
ELIXIR: 0,
|
ELIXIR: 0,
|
||||||
ERLANG: 1,
|
ERLANG: 1,
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* WIP WIP WIP
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const reviver = (key, value) => {
|
||||||
|
if (typeof value === "object" && value.type === "atom") {
|
||||||
|
return Atom.toAtom(value.value);
|
||||||
|
}
|
||||||
|
else if (typeof value === "object" && value.type === "tuple") {
|
||||||
|
return new Tuple(value.values);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return value;
|
||||||
|
};
|
||||||
const log = (msg) => process.stderr.write(`${msg}\r\n`);
|
const log = (msg) => process.stderr.write(`${msg}\r\n`);
|
||||||
|
/**
|
||||||
|
* Return a subarray of the supplied buffer, minus len bytes from the front.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* @param len Number of bytes to chop off front
|
||||||
|
* @returns Subarray of buffer
|
||||||
|
*/
|
||||||
|
const chopl = (buffer, len = 1) => buffer.subarray(len);
|
||||||
|
export class Atom {
|
||||||
|
value;
|
||||||
|
constructor(value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
static atoms = {};
|
||||||
|
static toAtom = (value) => {
|
||||||
|
return Atom.atoms[value] || (Atom.atoms[value] = new Atom(value));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* WIP WIP WIP
|
||||||
|
*/
|
||||||
|
export class Tuple {
|
||||||
|
values;
|
||||||
|
constructor(values) {
|
||||||
|
// Yes right now this is only one level deep.
|
||||||
|
this.values = [...values];
|
||||||
|
}
|
||||||
|
}
|
||||||
export class Bert {
|
export class Bert {
|
||||||
allBinariesAsString;
|
allBinariesAsString;
|
||||||
mapKeyAsAtom;
|
mapKeyAsAtom;
|
||||||
|
@ -56,7 +105,7 @@ export class Bert {
|
||||||
};
|
};
|
||||||
#decode = (buffer) => {
|
#decode = (buffer) => {
|
||||||
const t = buffer[0];
|
const t = buffer[0];
|
||||||
buffer = buffer.subarray(1);
|
buffer = chopl(buffer);
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case Types.SMALL_ATOM:
|
case Types.SMALL_ATOM:
|
||||||
return this.decode_atom(buffer, 1);
|
return this.decode_atom(buffer, 1);
|
||||||
|
@ -96,7 +145,7 @@ export class Bert {
|
||||||
if (buffer[0] !== Types.BERT_START) {
|
if (buffer[0] !== Types.BERT_START) {
|
||||||
throw new Error("Invalid BERT start magic");
|
throw new Error("Invalid BERT start magic");
|
||||||
}
|
}
|
||||||
const obj = this.#decode(buffer.subarray(1));
|
const obj = this.#decode(chopl(buffer));
|
||||||
if (obj.rest.length !== 0) {
|
if (obj.rest.length !== 0) {
|
||||||
throw new Error(`Invalid BERT, remainder was: ${obj.rest.length}`);
|
throw new Error(`Invalid BERT, remainder was: ${obj.rest.length}`);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +302,7 @@ export class Bert {
|
||||||
};
|
};
|
||||||
decode_atom = (buffer, count) => {
|
decode_atom = (buffer, count) => {
|
||||||
const size = this.bytesToInt(buffer, count, true);
|
const size = this.bytesToInt(buffer, count, true);
|
||||||
buffer = buffer.subarray(count);
|
buffer = chopl(buffer, count);
|
||||||
let value = buffer.toString("utf8", 0, size);
|
let value = buffer.toString("utf8", 0, size);
|
||||||
if (value === "true") {
|
if (value === "true") {
|
||||||
value = true;
|
value = true;
|
||||||
|
@ -276,33 +325,33 @@ export class Bert {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_binary = (buffer) => {
|
decode_binary = (buffer) => {
|
||||||
const size = this.bytesToInt(buffer, 4, true);
|
const size = this.bytesToInt(buffer, 4, true);
|
||||||
buffer = buffer.subarray(4);
|
buffer = chopl(buffer, 4);
|
||||||
const bin = Buffer.alloc(size);
|
const bin = Buffer.alloc(size);
|
||||||
buffer.copy(bin, 0, 0, size);
|
buffer.copy(bin, 0, 0, size);
|
||||||
return {
|
return {
|
||||||
value: this.convention === Lang.ELIXIR && this.allBinariesAsString
|
value: this.convention === Lang.ELIXIR && this.allBinariesAsString
|
||||||
? bin.toString("utf-8")
|
? bin.toString("utf-8")
|
||||||
: bin,
|
: bin,
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_integer = (buffer, count, unsigned = false) => {
|
decode_integer = (buffer, count, unsigned = false) => {
|
||||||
return {
|
return {
|
||||||
value: this.bytesToInt(buffer, count, unsigned),
|
value: this.bytesToInt(buffer, count, unsigned),
|
||||||
rest: buffer.subarray(count),
|
rest: chopl(buffer, count),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_big = (buffer, count) => {
|
decode_big = (buffer, count) => {
|
||||||
const size = this.bytesToInt(buffer, count, false);
|
const size = this.bytesToInt(buffer, count, true);
|
||||||
buffer = buffer.subarray(count);
|
buffer = chopl(buffer, count);
|
||||||
let num = 0;
|
let num = 0;
|
||||||
const isNegative = buffer[0] === 1;
|
const isNegative = buffer[0] === 1;
|
||||||
buffer = buffer.subarray(1);
|
buffer = chopl(buffer);
|
||||||
for (let i = size - 1; i >= 0; --i) {
|
for (let i = size - 1; i >= 0; --i) {
|
||||||
const n = buffer[i];
|
const n = buffer[i];
|
||||||
if (num === 0) {
|
if (num === 0) {
|
||||||
|
@ -317,35 +366,35 @@ export class Bert {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
value: num,
|
value: num,
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_float = (buffer) => {
|
decode_float = (buffer) => {
|
||||||
const size = 31;
|
const size = 31;
|
||||||
return {
|
return {
|
||||||
value: parseFloat(buffer.toString("utf8", 0, size)),
|
value: parseFloat(buffer.toString("utf8", 0, size)),
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_new_float = (buffer) => {
|
decode_new_float = (buffer) => {
|
||||||
return {
|
return {
|
||||||
value: buffer.readDoubleBE(0),
|
value: buffer.readDoubleBE(0),
|
||||||
rest: buffer.subarray(8),
|
rest: chopl(buffer, 8),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_string = (buffer) => {
|
decode_string = (buffer) => {
|
||||||
const sizeLen = this.convention === Lang.ELIXIR ? 4 : 2;
|
const sizeLen = this.convention === Lang.ELIXIR ? 4 : 2;
|
||||||
const size = this.bytesToInt(buffer, sizeLen, true);
|
const size = this.bytesToInt(buffer, sizeLen, true);
|
||||||
buffer = buffer.subarray(sizeLen);
|
buffer = chopl(buffer, sizeLen);
|
||||||
return {
|
return {
|
||||||
value: buffer.toString("utf8", 0, size),
|
value: buffer.toString("utf8", 0, size),
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
decode_list = (buffer) => {
|
decode_list = (buffer) => {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
const size = this.bytesToInt(buffer, 4, true);
|
const size = this.bytesToInt(buffer, 4, true);
|
||||||
buffer = buffer.subarray(4);
|
buffer = chopl(buffer, 4);
|
||||||
for (let i = 0; i < size; ++i) {
|
for (let i = 0; i < size; ++i) {
|
||||||
const el = this.#decode(buffer);
|
const el = this.#decode(buffer);
|
||||||
arr.push(el.value);
|
arr.push(el.value);
|
||||||
|
@ -355,7 +404,7 @@ export class Bert {
|
||||||
if (lastChar !== Types.NIL) {
|
if (lastChar !== Types.NIL) {
|
||||||
throw new Error("List does not end with NIL");
|
throw new Error("List does not end with NIL");
|
||||||
}
|
}
|
||||||
buffer = buffer.subarray(1);
|
buffer = chopl(buffer);
|
||||||
return {
|
return {
|
||||||
value: arr,
|
value: arr,
|
||||||
rest: buffer,
|
rest: buffer,
|
||||||
|
@ -364,7 +413,7 @@ export class Bert {
|
||||||
decode_map = (buffer) => {
|
decode_map = (buffer) => {
|
||||||
const map = {};
|
const map = {};
|
||||||
const size = this.bytesToInt(buffer, 4, true);
|
const size = this.bytesToInt(buffer, 4, true);
|
||||||
buffer = buffer.subarray(4);
|
buffer = chopl(buffer, 4);
|
||||||
for (let i = 0; i < size; ++i) {
|
for (let i = 0; i < size; ++i) {
|
||||||
let el = this.#decode(buffer);
|
let el = this.#decode(buffer);
|
||||||
const key = el.value;
|
const key = el.value;
|
||||||
|
@ -381,7 +430,7 @@ export class Bert {
|
||||||
decode_tuple = (buffer, count) => {
|
decode_tuple = (buffer, count) => {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
const size = this.bytesToInt(buffer, count, true);
|
const size = this.bytesToInt(buffer, count, true);
|
||||||
buffer = buffer.subarray(count);
|
buffer = chopl(buffer, count);
|
||||||
for (let i = 0; i < size; ++i) {
|
for (let i = 0; i < size; ++i) {
|
||||||
const el = this.#decode(buffer);
|
const el = this.#decode(buffer);
|
||||||
arr.push(el.value);
|
arr.push(el.value);
|
||||||
|
@ -409,12 +458,6 @@ export class Bert {
|
||||||
return unsigned ? buffer.readUInt32BE(0) : buffer.readInt32BE(0);
|
return unsigned ? buffer.readUInt32BE(0) : buffer.readInt32BE(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
binary_to_list = (str) => {
|
|
||||||
const ret = [];
|
|
||||||
for (let i = 0; i < str.length; ++i)
|
|
||||||
ret.push(str[i]);
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Convert object to atom.
|
* Convert object to atom.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Bert } from "./bert.js";
|
import { Bert } from "./bert.js";
|
||||||
import { Duplex } from "node:stream";
|
import { Duplex } from "node:stream";
|
||||||
|
const log = (msg) => process.stderr.write(`${msg}\r\n`);
|
||||||
export class Port extends Duplex {
|
export class Port extends Duplex {
|
||||||
bert;
|
bert;
|
||||||
originalStdout;
|
originalStdout;
|
||||||
|
@ -17,7 +18,7 @@ export class Port extends Duplex {
|
||||||
const lenBytes = process.stdin.read(4);
|
const lenBytes = process.stdin.read(4);
|
||||||
if (lenBytes) {
|
if (lenBytes) {
|
||||||
const termLen = this.bert.bytesToInt(lenBytes, 4, true);
|
const termLen = this.bert.bytesToInt(lenBytes, 4, true);
|
||||||
process.stderr.write(`Got incoming term length: ${termLen} (bytes: <<${lenBytes.toString('hex').match(/../g).map((hex) => parseInt(`0x${hex}`).toString()).join(', ')}>>)\n`);
|
log(`Got incoming term length: ${termLen} (bytes: <<${lenBytes.toString('hex').match(/../g).map((hex) => parseInt(`0x${hex}`).toString()).join(', ')}>>).`);
|
||||||
const termBytes = process.stdin.read(termLen);
|
const termBytes = process.stdin.read(termLen);
|
||||||
if (termBytes) {
|
if (termBytes) {
|
||||||
const decoded = this.bert.decode(termBytes);
|
const decoded = this.bert.decode(termBytes);
|
||||||
|
@ -25,7 +26,7 @@ export class Port extends Duplex {
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
process.stderr.write(`Term read got erroneous null.\n`);
|
log("Term read got erroneous null.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +46,7 @@ export class Port extends Duplex {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
process.stderr.write(`Error writing: ${error}\n`);
|
log(`Error writing: ${error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
132
src/bert.ts
132
src/bert.ts
|
@ -1,3 +1,8 @@
|
||||||
|
/**
|
||||||
|
* TODO: bigint, large decimals. For right now, only use numbers that fit in
|
||||||
|
* JavaScript number.
|
||||||
|
*/
|
||||||
|
|
||||||
export const Types = {
|
export const Types = {
|
||||||
BERT_START: 131,
|
BERT_START: 131,
|
||||||
SMALL_ATOM: 119,
|
SMALL_ATOM: 119,
|
||||||
|
@ -23,8 +28,63 @@ export const Lang = {
|
||||||
ERLANG: 1,
|
ERLANG: 1,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
interface Partial<Type> {
|
||||||
|
value: Type;
|
||||||
|
rest: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WIP WIP WIP
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const reviver = (key: string, value: any) => {
|
||||||
|
if (typeof value === "object" && value.type === "atom") {
|
||||||
|
return Atom.toAtom(value.value);
|
||||||
|
}
|
||||||
|
else if (typeof value === "object" && value.type === "tuple") {
|
||||||
|
return new Tuple(value.values);
|
||||||
|
}
|
||||||
|
else return value;
|
||||||
|
};
|
||||||
|
|
||||||
const log = (msg: string) => process.stderr.write(`${msg}\r\n`);
|
const log = (msg: string) => process.stderr.write(`${msg}\r\n`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a subarray of the supplied buffer, minus len bytes from the front.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* @param len Number of bytes to chop off front
|
||||||
|
* @returns Subarray of buffer
|
||||||
|
*/
|
||||||
|
const chopl = (buffer: Buffer, len = 1) => buffer.subarray(len);
|
||||||
|
|
||||||
|
export class Atom {
|
||||||
|
public readonly value;
|
||||||
|
private constructor(value: string) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static atoms: Record<string, Atom> = {};
|
||||||
|
|
||||||
|
public static toAtom = (value: string) => {
|
||||||
|
return Atom.atoms[value] || (Atom.atoms[value] = new Atom(value));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WIP WIP WIP
|
||||||
|
*/
|
||||||
|
export class Tuple {
|
||||||
|
public readonly values;
|
||||||
|
constructor(values: any[]) {
|
||||||
|
// Yes right now this is only one level deep.
|
||||||
|
this.values = [...values];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Bert {
|
export class Bert {
|
||||||
private allBinariesAsString;
|
private allBinariesAsString;
|
||||||
private mapKeyAsAtom;
|
private mapKeyAsAtom;
|
||||||
|
@ -74,7 +134,7 @@ export class Bert {
|
||||||
|
|
||||||
#decode = (buffer: Buffer): any => {
|
#decode = (buffer: Buffer): any => {
|
||||||
const t = buffer[0];
|
const t = buffer[0];
|
||||||
buffer = buffer.subarray(1);
|
buffer = chopl(buffer);
|
||||||
|
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case Types.SMALL_ATOM:
|
case Types.SMALL_ATOM:
|
||||||
|
@ -117,7 +177,7 @@ export class Bert {
|
||||||
throw new Error("Invalid BERT start magic");
|
throw new Error("Invalid BERT start magic");
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = this.#decode(buffer.subarray(1));
|
const obj = this.#decode(chopl(buffer));
|
||||||
|
|
||||||
if (obj.rest.length !== 0) {
|
if (obj.rest.length !== 0) {
|
||||||
throw new Error(`Invalid BERT, remainder was: ${obj.rest.length}`);
|
throw new Error(`Invalid BERT, remainder was: ${obj.rest.length}`);
|
||||||
|
@ -191,7 +251,8 @@ export class Bert {
|
||||||
buffer.writeUInt8(offset - 1, 1);
|
buffer.writeUInt8(offset - 1, 1);
|
||||||
numBuffer.copy(buffer, 2, 0, offset);
|
numBuffer.copy(buffer, 2, 0, offset);
|
||||||
return buffer.subarray(0, 2 + offset);
|
return buffer.subarray(0, 2 + offset);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
buffer[0] = Types.LARGE_BIG;
|
buffer[0] = Types.LARGE_BIG;
|
||||||
buffer.writeUInt32BE(offset - 1, 1);
|
buffer.writeUInt32BE(offset - 1, 1);
|
||||||
numBuffer.copy(buffer, 5, 0, offset);
|
numBuffer.copy(buffer, 5, 0, offset);
|
||||||
|
@ -297,9 +358,9 @@ export class Bert {
|
||||||
return buffer;
|
return buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_atom = (buffer: Buffer, count: 1 | 2 | 4) => {
|
decode_atom = (buffer: Buffer, count: 1 | 2 | 4): Partial<any> => {
|
||||||
const size = this.bytesToInt(buffer, count, true);
|
const size = this.bytesToInt(buffer, count, true);
|
||||||
buffer = buffer.subarray(count);
|
buffer = chopl(buffer, count);
|
||||||
let value: any = buffer.toString("utf8", 0, size);
|
let value: any = buffer.toString("utf8", 0, size);
|
||||||
if (value === "true") {
|
if (value === "true") {
|
||||||
value = true;
|
value = true;
|
||||||
|
@ -327,13 +388,13 @@ export class Bert {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_binary = (buffer: Buffer) => {
|
decode_binary = (buffer: Buffer): Partial<string | Buffer> => {
|
||||||
const size = this.bytesToInt(buffer, 4, true);
|
const size = this.bytesToInt(buffer, 4, true);
|
||||||
buffer = buffer.subarray(4);
|
buffer = chopl(buffer, 4);
|
||||||
const bin = Buffer.alloc(size);
|
const bin = Buffer.alloc(size);
|
||||||
buffer.copy(bin, 0, 0, size);
|
buffer.copy(bin, 0, 0, size);
|
||||||
return {
|
return {
|
||||||
|
@ -341,31 +402,32 @@ export class Bert {
|
||||||
this.convention === Lang.ELIXIR && this.allBinariesAsString
|
this.convention === Lang.ELIXIR && this.allBinariesAsString
|
||||||
? bin.toString("utf-8")
|
? bin.toString("utf-8")
|
||||||
: bin,
|
: bin,
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_integer = (buffer: Buffer, count: 1 | 2 | 4, unsigned = false) => {
|
decode_integer = (buffer: Buffer, count: 1 | 2 | 4, unsigned = false): Partial<number> => {
|
||||||
return {
|
return {
|
||||||
value: this.bytesToInt(buffer, count, unsigned),
|
value: this.bytesToInt(buffer, count, unsigned),
|
||||||
rest: buffer.subarray(count),
|
rest: chopl(buffer, count),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_big = (buffer: Buffer, count: 1 | 2 | 4) => {
|
decode_big = (buffer: Buffer, count: 1 | 2 | 4) => {
|
||||||
const size = this.bytesToInt(buffer, count, false);
|
const size = this.bytesToInt(buffer, count, true);
|
||||||
buffer = buffer.subarray(count);
|
buffer = chopl(buffer, count);
|
||||||
|
|
||||||
let num = 0;
|
let num = 0;
|
||||||
const isNegative = buffer[0] === 1;
|
const isNegative = buffer[0] === 1;
|
||||||
|
|
||||||
buffer = buffer.subarray(1);
|
buffer = chopl(buffer);
|
||||||
|
|
||||||
for (let i = size - 1; i >= 0; --i) {
|
for (let i = size - 1; i >= 0; --i) {
|
||||||
const n = buffer[i];
|
const n = buffer[i];
|
||||||
if (num === 0) {
|
if (num === 0) {
|
||||||
num = n;
|
num = n;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
num = num * 256 + n;
|
num = num * 256 + n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,41 +437,41 @@ export class Bert {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: num,
|
value: num,
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_float = (buffer: Buffer) => {
|
decode_float = (buffer: Buffer): Partial<number> => {
|
||||||
const size = 31;
|
const size = 31;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: parseFloat(buffer.toString("utf8", 0, size)),
|
value: parseFloat(buffer.toString("utf8", 0, size)),
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_new_float = (buffer: Buffer) => {
|
decode_new_float = (buffer: Buffer): Partial<number> => {
|
||||||
return {
|
return {
|
||||||
value: buffer.readDoubleBE(0),
|
value: buffer.readDoubleBE(0),
|
||||||
rest: buffer.subarray(8),
|
rest: chopl(buffer, 8),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_string = (buffer: Buffer) => {
|
decode_string = (buffer: Buffer): Partial<string> => {
|
||||||
const sizeLen = this.convention === Lang.ELIXIR ? 4 : 2;
|
const sizeLen = this.convention === Lang.ELIXIR ? 4 : 2;
|
||||||
const size = this.bytesToInt(buffer, sizeLen, true);
|
const size = this.bytesToInt(buffer, sizeLen, true);
|
||||||
buffer = buffer.subarray(sizeLen);
|
buffer = chopl(buffer, sizeLen);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: buffer.toString("utf8", 0, size),
|
value: buffer.toString("utf8", 0, size),
|
||||||
rest: buffer.subarray(size),
|
rest: chopl(buffer, size),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_list = (buffer: Buffer) => {
|
decode_list = (buffer: Buffer): Partial<any[]> => {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
const size = this.bytesToInt(buffer, 4, true);
|
const size = this.bytesToInt(buffer, 4, true);
|
||||||
buffer = buffer.subarray(4);
|
buffer = chopl(buffer, 4);
|
||||||
|
|
||||||
for (let i = 0; i < size; ++i) {
|
for (let i = 0; i < size; ++i) {
|
||||||
const el = this.#decode(buffer);
|
const el = this.#decode(buffer);
|
||||||
|
@ -423,7 +485,7 @@ export class Bert {
|
||||||
throw new Error("List does not end with NIL");
|
throw new Error("List does not end with NIL");
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = buffer.subarray(1);
|
buffer = chopl(buffer);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: arr,
|
value: arr,
|
||||||
|
@ -431,11 +493,11 @@ export class Bert {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_map = (buffer: Buffer) => {
|
decode_map = (buffer: Buffer): Partial<Record<string, any>> => {
|
||||||
const map: Record<string, any> = {};
|
const map: Record<string, any> = {};
|
||||||
const size = this.bytesToInt(buffer, 4, true);
|
const size = this.bytesToInt(buffer, 4, true);
|
||||||
|
|
||||||
buffer = buffer.subarray(4);
|
buffer = chopl(buffer, 4);
|
||||||
|
|
||||||
for (let i = 0; i < size; ++i) {
|
for (let i = 0; i < size; ++i) {
|
||||||
let el = this.#decode(buffer);
|
let el = this.#decode(buffer);
|
||||||
|
@ -451,22 +513,23 @@ export class Bert {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_tuple = (buffer: Buffer, count: 1 | 2 | 4) => {
|
decode_tuple = (buffer: Buffer, count: 1 | 2 | 4): Partial<any> => {
|
||||||
const arr = [];
|
const arr = [];
|
||||||
const size = this.bytesToInt(buffer, count, true);
|
const size = this.bytesToInt(buffer, count, true);
|
||||||
buffer = buffer.subarray(count);
|
buffer = chopl(buffer, count);
|
||||||
for (let i = 0; i < size; ++i) {
|
for (let i = 0; i < size; ++i) {
|
||||||
const el = this.#decode(buffer);
|
const el = this.#decode(buffer);
|
||||||
arr.push(el.value);
|
arr.push(el.value);
|
||||||
buffer = el.rest;
|
buffer = el.rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: this.toTuple(arr),
|
value: this.toTuple(arr),
|
||||||
rest: buffer,
|
rest: buffer,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
decode_nil = (buffer: Buffer) => {
|
decode_nil = (buffer: Buffer): Partial<[]> => {
|
||||||
// nil is an empty list
|
// nil is an empty list
|
||||||
return {
|
return {
|
||||||
value: [],
|
value: [],
|
||||||
|
@ -484,13 +547,6 @@ export class Bert {
|
||||||
return unsigned ? buffer.readUInt32BE(0) : buffer.readInt32BE(0);
|
return unsigned ? buffer.readUInt32BE(0) : buffer.readInt32BE(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
binary_to_list = (str: string) => {
|
|
||||||
const ret = [];
|
|
||||||
for (let i = 0; i < str.length; ++i) ret.push(str[i]);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Duplex } from "node:stream";
|
||||||
|
|
||||||
type WriteCallback = (error: Error | null | undefined) => void;
|
type WriteCallback = (error: Error | null | undefined) => void;
|
||||||
|
|
||||||
|
const log = (msg: string) => process.stderr.write(`${msg}\r\n`);
|
||||||
|
|
||||||
export class Port extends Duplex {
|
export class Port extends Duplex {
|
||||||
public readonly bert: Bert;
|
public readonly bert: Bert;
|
||||||
private originalStdout;
|
private originalStdout;
|
||||||
|
@ -22,7 +24,7 @@ export class Port extends Duplex {
|
||||||
const lenBytes = process.stdin.read(4);
|
const lenBytes = process.stdin.read(4);
|
||||||
if (lenBytes) {
|
if (lenBytes) {
|
||||||
const termLen = this.bert.bytesToInt(lenBytes, 4, true);
|
const termLen = this.bert.bytesToInt(lenBytes, 4, true);
|
||||||
process.stderr.write(`Got incoming term length: ${termLen} (bytes: <<${lenBytes.toString('hex').match(/../g).map((hex: string) => parseInt(`0x${hex}`).toString()).join(', ')}>>)\n`);
|
log(`Got incoming term length: ${termLen} (bytes: <<${lenBytes.toString('hex').match(/../g).map((hex: string) => parseInt(`0x${hex}`).toString()).join(', ')}>>).`);
|
||||||
const termBytes = process.stdin.read(termLen);
|
const termBytes = process.stdin.read(termLen);
|
||||||
|
|
||||||
if (termBytes) {
|
if (termBytes) {
|
||||||
|
@ -31,7 +33,7 @@ export class Port extends Duplex {
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
process.stderr.write(`Term read got erroneous null.\n`);
|
log("Term read got erroneous null.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +55,7 @@ export class Port extends Duplex {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
process.stderr.write(`Error writing: ${error}\n`);
|
log(`Error writing: ${error}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue