diff --git a/dist/bert.d.ts b/dist/bert.d.ts index ac3d9d9..059e7bd 100644 --- a/dist/bert.d.ts +++ b/dist/bert.d.ts @@ -1,3 +1,7 @@ +/** + * TODO: bigint, large decimals. For right now, only use numbers that fit in + * JavaScript number. + */ /// export declare const Types: { readonly BERT_START: 131; @@ -22,6 +26,23 @@ export declare const Lang: { readonly ELIXIR: 0; readonly ERLANG: 1; }; +interface Partial { + 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 { #private; private allBinariesAsString; @@ -147,150 +168,21 @@ export declare class Bert { encode_tuple: (obj: Buffer, buffer: Buffer) => Buffer; encode_array: (obj: any[], buffer: Buffer) => Buffer; encode_map: (obj: Record, buffer: Buffer) => Buffer; - decode_atom: (buffer: Buffer, count: 1 | 2 | 4) => { - value: any; - rest: Buffer; - }; - decode_binary: (buffer: Buffer) => { - value: string | Buffer; - rest: Buffer; - }; - decode_integer: (buffer: Buffer, count: 1 | 2 | 4, unsigned?: boolean) => { - value: number; - rest: Buffer; - }; + decode_atom: (buffer: Buffer, count: 1 | 2 | 4) => Partial; + decode_binary: (buffer: Buffer) => Partial; + decode_integer: (buffer: Buffer, count: 1 | 2 | 4, unsigned?: boolean) => Partial; decode_big: (buffer: Buffer, count: 1 | 2 | 4) => { value: number; rest: Buffer; }; - decode_float: (buffer: Buffer) => { - value: number; - rest: Buffer; - }; - decode_new_float: (buffer: Buffer) => { - value: number; - rest: Buffer; - }; - decode_string: (buffer: Buffer) => { - value: string; - rest: Buffer; - }; - decode_list: (buffer: Buffer) => { - value: any[]; - rest: Buffer; - }; - decode_map: (buffer: Buffer) => { - value: Record; - 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[]; - 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(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(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any): U[]; - filter(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(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(callbackfn: (previousValue: U_2, currentValue: any, currentIndex: number, array: any[]) => U_2, initialValue: U_2): U_2; - find(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; - values(): IterableIterator; - includes(searchElement: any, fromIndex?: number | undefined): boolean; - flatMap(callback: (this: This, value: any, index: number, array: any[]) => U_3 | readonly U_3[], thisArg?: This | undefined): U_3[]; - flat(this: A, depth?: D | undefined): FlatArray[]; - at(index: number): any; - findLast(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; - [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; - }; + decode_float: (buffer: Buffer) => Partial; + decode_new_float: (buffer: Buffer) => Partial; + decode_string: (buffer: Buffer) => Partial; + decode_list: (buffer: Buffer) => Partial; + decode_map: (buffer: Buffer) => Partial>; + decode_tuple: (buffer: Buffer, count: 1 | 2 | 4) => Partial; + decode_nil: (buffer: Buffer) => Partial<[]>; bytesToInt: (buffer: Buffer, length: 1 | 2 | 4, unsigned: boolean) => number; - binary_to_list: (str: string) => string[]; } /** * Convert object to atom. @@ -404,3 +296,4 @@ export declare const toTuple: (arr: any[]) => { readonly [Symbol.unscopables]?: boolean | undefined; }; }; +export {}; diff --git a/dist/bert.js b/dist/bert.js index 0fcb7e5..60b7944 100644 --- a/dist/bert.js +++ b/dist/bert.js @@ -1,3 +1,7 @@ +/** + * TODO: bigint, large decimals. For right now, only use numbers that fit in + * JavaScript number. + */ export const Types = { BERT_START: 131, SMALL_ATOM: 119, @@ -21,7 +25,52 @@ export const Lang = { ELIXIR: 0, 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`); +/** + * 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 { allBinariesAsString; mapKeyAsAtom; @@ -56,7 +105,7 @@ export class Bert { }; #decode = (buffer) => { const t = buffer[0]; - buffer = buffer.subarray(1); + buffer = chopl(buffer); switch (t) { case Types.SMALL_ATOM: return this.decode_atom(buffer, 1); @@ -96,7 +145,7 @@ export class Bert { if (buffer[0] !== Types.BERT_START) { 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) { throw new Error(`Invalid BERT, remainder was: ${obj.rest.length}`); } @@ -253,7 +302,7 @@ export class Bert { }; decode_atom = (buffer, count) => { const size = this.bytesToInt(buffer, count, true); - buffer = buffer.subarray(count); + buffer = chopl(buffer, count); let value = buffer.toString("utf8", 0, size); if (value === "true") { value = true; @@ -276,33 +325,33 @@ export class Bert { } return { value, - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; decode_binary = (buffer) => { const size = this.bytesToInt(buffer, 4, true); - buffer = buffer.subarray(4); + buffer = chopl(buffer, 4); const bin = Buffer.alloc(size); buffer.copy(bin, 0, 0, size); return { value: this.convention === Lang.ELIXIR && this.allBinariesAsString ? bin.toString("utf-8") : bin, - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; decode_integer = (buffer, count, unsigned = false) => { return { value: this.bytesToInt(buffer, count, unsigned), - rest: buffer.subarray(count), + rest: chopl(buffer, count), }; }; decode_big = (buffer, count) => { - const size = this.bytesToInt(buffer, count, false); - buffer = buffer.subarray(count); + const size = this.bytesToInt(buffer, count, true); + buffer = chopl(buffer, count); let num = 0; const isNegative = buffer[0] === 1; - buffer = buffer.subarray(1); + buffer = chopl(buffer); for (let i = size - 1; i >= 0; --i) { const n = buffer[i]; if (num === 0) { @@ -317,35 +366,35 @@ export class Bert { } return { value: num, - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; decode_float = (buffer) => { const size = 31; return { value: parseFloat(buffer.toString("utf8", 0, size)), - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; decode_new_float = (buffer) => { return { value: buffer.readDoubleBE(0), - rest: buffer.subarray(8), + rest: chopl(buffer, 8), }; }; decode_string = (buffer) => { const sizeLen = this.convention === Lang.ELIXIR ? 4 : 2; const size = this.bytesToInt(buffer, sizeLen, true); - buffer = buffer.subarray(sizeLen); + buffer = chopl(buffer, sizeLen); return { value: buffer.toString("utf8", 0, size), - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; decode_list = (buffer) => { const arr = []; const size = this.bytesToInt(buffer, 4, true); - buffer = buffer.subarray(4); + buffer = chopl(buffer, 4); for (let i = 0; i < size; ++i) { const el = this.#decode(buffer); arr.push(el.value); @@ -355,7 +404,7 @@ export class Bert { if (lastChar !== Types.NIL) { throw new Error("List does not end with NIL"); } - buffer = buffer.subarray(1); + buffer = chopl(buffer); return { value: arr, rest: buffer, @@ -364,7 +413,7 @@ export class Bert { decode_map = (buffer) => { const map = {}; const size = this.bytesToInt(buffer, 4, true); - buffer = buffer.subarray(4); + buffer = chopl(buffer, 4); for (let i = 0; i < size; ++i) { let el = this.#decode(buffer); const key = el.value; @@ -381,7 +430,7 @@ export class Bert { decode_tuple = (buffer, count) => { const arr = []; const size = this.bytesToInt(buffer, count, true); - buffer = buffer.subarray(count); + buffer = chopl(buffer, count); for (let i = 0; i < size; ++i) { const el = this.#decode(buffer); arr.push(el.value); @@ -409,12 +458,6 @@ export class Bert { 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. diff --git a/dist/main.js b/dist/main.js index 79cb43c..d55002a 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,5 +1,6 @@ import { Bert } from "./bert.js"; import { Duplex } from "node:stream"; +const log = (msg) => process.stderr.write(`${msg}\r\n`); export class Port extends Duplex { bert; originalStdout; @@ -17,7 +18,7 @@ export class Port extends Duplex { const lenBytes = process.stdin.read(4); if (lenBytes) { 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); if (termBytes) { const decoded = this.bert.decode(termBytes); @@ -25,7 +26,7 @@ export class Port extends Duplex { return decoded; } else { - process.stderr.write(`Term read got erroneous null.\n`); + log("Term read got erroneous null."); return null; } } @@ -45,7 +46,7 @@ export class Port extends Duplex { return true; } catch (error) { - process.stderr.write(`Error writing: ${error}\n`); + log(`Error writing: ${error}`); return false; } } diff --git a/src/bert.ts b/src/bert.ts index 6c77d18..4d6ba08 100644 --- a/src/bert.ts +++ b/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 = { BERT_START: 131, SMALL_ATOM: 119, @@ -23,8 +28,63 @@ export const Lang = { ERLANG: 1, } as const; +interface Partial { + 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`); +/** + * 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 = {}; + + 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 { private allBinariesAsString; private mapKeyAsAtom; @@ -74,7 +134,7 @@ export class Bert { #decode = (buffer: Buffer): any => { const t = buffer[0]; - buffer = buffer.subarray(1); + buffer = chopl(buffer); switch (t) { case Types.SMALL_ATOM: @@ -117,7 +177,7 @@ export class Bert { 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) { throw new Error(`Invalid BERT, remainder was: ${obj.rest.length}`); @@ -191,7 +251,8 @@ export class Bert { buffer.writeUInt8(offset - 1, 1); numBuffer.copy(buffer, 2, 0, offset); return buffer.subarray(0, 2 + offset); - } else { + } + else { buffer[0] = Types.LARGE_BIG; buffer.writeUInt32BE(offset - 1, 1); numBuffer.copy(buffer, 5, 0, offset); @@ -297,9 +358,9 @@ export class Bert { return buffer; }; - decode_atom = (buffer: Buffer, count: 1 | 2 | 4) => { + decode_atom = (buffer: Buffer, count: 1 | 2 | 4): Partial => { const size = this.bytesToInt(buffer, count, true); - buffer = buffer.subarray(count); + buffer = chopl(buffer, count); let value: any = buffer.toString("utf8", 0, size); if (value === "true") { value = true; @@ -327,13 +388,13 @@ export class Bert { return { value, - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; - decode_binary = (buffer: Buffer) => { + decode_binary = (buffer: Buffer): Partial => { const size = this.bytesToInt(buffer, 4, true); - buffer = buffer.subarray(4); + buffer = chopl(buffer, 4); const bin = Buffer.alloc(size); buffer.copy(bin, 0, 0, size); return { @@ -341,31 +402,32 @@ export class Bert { this.convention === Lang.ELIXIR && this.allBinariesAsString ? bin.toString("utf-8") : 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 => { return { value: this.bytesToInt(buffer, count, unsigned), - rest: buffer.subarray(count), + rest: chopl(buffer, count), }; }; decode_big = (buffer: Buffer, count: 1 | 2 | 4) => { - const size = this.bytesToInt(buffer, count, false); - buffer = buffer.subarray(count); + const size = this.bytesToInt(buffer, count, true); + buffer = chopl(buffer, count); let num = 0; const isNegative = buffer[0] === 1; - buffer = buffer.subarray(1); + buffer = chopl(buffer); for (let i = size - 1; i >= 0; --i) { const n = buffer[i]; if (num === 0) { num = n; - } else { + } + else { num = num * 256 + n; } } @@ -375,41 +437,41 @@ export class Bert { return { value: num, - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; - decode_float = (buffer: Buffer) => { + decode_float = (buffer: Buffer): Partial => { const size = 31; return { 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 => { return { value: buffer.readDoubleBE(0), - rest: buffer.subarray(8), + rest: chopl(buffer, 8), }; }; - decode_string = (buffer: Buffer) => { + decode_string = (buffer: Buffer): Partial => { const sizeLen = this.convention === Lang.ELIXIR ? 4 : 2; const size = this.bytesToInt(buffer, sizeLen, true); - buffer = buffer.subarray(sizeLen); + buffer = chopl(buffer, sizeLen); return { value: buffer.toString("utf8", 0, size), - rest: buffer.subarray(size), + rest: chopl(buffer, size), }; }; - decode_list = (buffer: Buffer) => { + decode_list = (buffer: Buffer): Partial => { const arr = []; const size = this.bytesToInt(buffer, 4, true); - buffer = buffer.subarray(4); + buffer = chopl(buffer, 4); for (let i = 0; i < size; ++i) { const el = this.#decode(buffer); @@ -423,7 +485,7 @@ export class Bert { throw new Error("List does not end with NIL"); } - buffer = buffer.subarray(1); + buffer = chopl(buffer); return { value: arr, @@ -431,11 +493,11 @@ export class Bert { }; }; - decode_map = (buffer: Buffer) => { + decode_map = (buffer: Buffer): Partial> => { const map: Record = {}; const size = this.bytesToInt(buffer, 4, true); - buffer = buffer.subarray(4); + buffer = chopl(buffer, 4); for (let i = 0; i < size; ++i) { 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 => { const arr = []; const size = this.bytesToInt(buffer, count, true); - buffer = buffer.subarray(count); + buffer = chopl(buffer, count); for (let i = 0; i < size; ++i) { const el = this.#decode(buffer); arr.push(el.value); buffer = el.rest; } + return { value: this.toTuple(arr), rest: buffer, }; }; - decode_nil = (buffer: Buffer) => { + decode_nil = (buffer: Buffer): Partial<[]> => { // nil is an empty list return { value: [], @@ -484,13 +547,6 @@ export class Bert { 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; - }; } /** diff --git a/src/main.ts b/src/main.ts index 8ba8e4e..ff685c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,8 @@ import { Duplex } from "node:stream"; type WriteCallback = (error: Error | null | undefined) => void; +const log = (msg: string) => process.stderr.write(`${msg}\r\n`); + export class Port extends Duplex { public readonly bert: Bert; private originalStdout; @@ -22,7 +24,7 @@ export class Port extends Duplex { const lenBytes = process.stdin.read(4); if (lenBytes) { 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); if (termBytes) { @@ -31,7 +33,7 @@ export class Port extends Duplex { return decoded; } else { - process.stderr.write(`Term read got erroneous null.\n`); + log("Term read got erroneous null."); return null; } } @@ -53,7 +55,7 @@ export class Port extends Duplex { return true; } catch (error) { - process.stderr.write(`Error writing: ${error}\n`); + log(`Error writing: ${error}`); return false; } }