harmony-erlpack 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/encode.ts ADDED
@@ -0,0 +1,170 @@
1
+ import {erlTypes, version} from "./erltype.js";
2
+
3
+ class ByteLayer {
4
+ private buffer = new ArrayBuffer(1 << 6);
5
+ private view = new DataView(this.buffer);
6
+ private index: number = 0;
7
+ private getSpace(len: number) {
8
+ if (this.index + len >= this.buffer.byteLength) {
9
+ let llen = this.buffer.byteLength;
10
+ while (this.index + len >= llen) llen *= 2;
11
+ this.buffer = this.buffer.transfer(llen);
12
+ this.view = new DataView(this.buffer);
13
+ }
14
+ const old = this.index;
15
+ this.index += len;
16
+ return old;
17
+ }
18
+ appendByte(num: number) {
19
+ const i = this.getSpace(1);
20
+ this.view.setInt8(i, num);
21
+ }
22
+ append32IntDelayed() {
23
+ const i = this.getSpace(4);
24
+ return (num: number) => {
25
+ this.view.setInt32(i, num);
26
+ };
27
+ }
28
+ append32Int(num: number) {
29
+ const i = this.getSpace(4);
30
+ this.view.setInt32(i, num);
31
+ }
32
+ append16Int(num: number) {
33
+ const i = this.getSpace(2);
34
+ this.view.setInt16(i, num);
35
+ }
36
+ appendDouble(num: number) {
37
+ const i = this.getSpace(8);
38
+ this.view.setFloat64(i, num);
39
+ }
40
+ getBuffer(len: number) {
41
+ const i = this.getSpace(len);
42
+ return {
43
+ buff: new Uint8Array(this.buffer, i),
44
+ ret: (num: number) => {
45
+ this.index -= num;
46
+ },
47
+ };
48
+ }
49
+ appendBuffer(buf: Uint8Array | number[]) {
50
+ const i = this.getSpace(buf.length);
51
+ //console.error(i, this.index, this.buffer.byteLength);
52
+ new Uint8Array(this.buffer).set(buf, i);
53
+ }
54
+ toBuffer() {
55
+ return this.buffer.transfer(this.index);
56
+ }
57
+ }
58
+ const encoder = new TextEncoder();
59
+ const preAtoms = new Map<string, Uint8Array>();
60
+
61
+ export function pack(obj: unknown) {
62
+ const layer = new ByteLayer();
63
+ const strMap = new Map<string, Uint8Array>();
64
+
65
+ function appendAtom(str: string) {
66
+ const buf = preAtoms.get(str) ?? encoder.encode(str);
67
+ if (!preAtoms.has(str)) preAtoms.set(str, buf);
68
+ layer.appendByte(erlTypes.sAtom);
69
+ layer.appendByte(buf.length);
70
+ layer.appendBuffer(buf);
71
+ }
72
+ function layBigint(d: bigint) {
73
+ const bytes = [] as number[];
74
+ const sign = d < 0n;
75
+ d = sign ? d * -1n : d;
76
+ while (d > 0n) {
77
+ bytes.push(Number(d & 0xffn));
78
+ d >>= 8n;
79
+ }
80
+ if (bytes.length < 255) {
81
+ layer.appendByte(erlTypes.sbigint);
82
+ layer.appendByte(bytes.length);
83
+ layer.appendByte(+sign);
84
+ layer.appendBuffer(bytes);
85
+ }
86
+ }
87
+ layer.appendByte(version);
88
+ function packObj(obj: unknown) {
89
+ switch (typeof obj) {
90
+ case "number": {
91
+ if (Number.isInteger(obj) && obj < 2 ** 32 && obj >= -(2 ** 31)) {
92
+ if (obj > 0 && obj < 256) {
93
+ layer.appendByte(erlTypes.u8);
94
+ layer.appendByte(obj);
95
+ } else if (obj < 2 ** 31 && obj >= -(2 ** 31)) {
96
+ layer.appendByte(erlTypes.i32);
97
+ layer.append32Int(obj);
98
+ } else {
99
+ layBigint(BigInt(obj));
100
+ }
101
+ } else {
102
+ layer.appendByte(erlTypes.double);
103
+ layer.appendDouble(obj);
104
+ }
105
+ break;
106
+ }
107
+ case "undefined": {
108
+ appendAtom("nil");
109
+ break;
110
+ }
111
+ case "boolean": {
112
+ appendAtom(obj + "");
113
+ break;
114
+ }
115
+ case "string": {
116
+ if (obj.length < 100) {
117
+ const buf = strMap.get(obj) ?? encoder.encode(obj);
118
+ strMap.set(obj, buf);
119
+ layer.appendByte(erlTypes.bin);
120
+ layer.append32Int(buf.length);
121
+ layer.appendBuffer(buf);
122
+ } else {
123
+ layer.appendByte(erlTypes.bin);
124
+ const len = layer.append32IntDelayed();
125
+ const size = obj.length * 3;
126
+ const {buff, ret} = layer.getBuffer(size);
127
+ const {written} = encoder.encodeInto(obj, buff);
128
+ ret(size - written);
129
+ len(written);
130
+ }
131
+
132
+ break;
133
+ }
134
+ case "object": {
135
+ if (obj instanceof Array) {
136
+ layer.appendByte(erlTypes.array);
137
+ layer.append32Int(obj.length);
138
+ for (const item of obj) {
139
+ packObj(item);
140
+ }
141
+ //Leaving this off will make @yukikaze-bot/erlpack segfault
142
+ layer.appendByte(erlTypes.endList);
143
+ } else if (obj === null) {
144
+ appendAtom("nil");
145
+ } else {
146
+ layer.appendByte(erlTypes.object);
147
+ const pairs = Object.entries(obj);
148
+ layer.append32Int(pairs.length);
149
+ for (const [key, value] of pairs) {
150
+ if (typeof value === "function" || typeof value === "symbol") continue;
151
+ packObj(key);
152
+ packObj(value);
153
+ }
154
+ }
155
+ break;
156
+ }
157
+ case "bigint": {
158
+ layBigint(obj);
159
+ break;
160
+ }
161
+ default: {
162
+ const type = typeof obj;
163
+ throw new Error(`unsupported type: ${type}`);
164
+ }
165
+ }
166
+ }
167
+
168
+ packObj(obj);
169
+ return layer.toBuffer();
170
+ }
package/src/erltype.ts ADDED
@@ -0,0 +1,14 @@
1
+ export const version = 131;
2
+ export const enum erlTypes {
3
+ u8 = 97,
4
+ sAtom = 115,
5
+ i32 = 98,
6
+ double = 70,
7
+ sbigint = 110,
8
+ lbigint = 111,
9
+ string16 = 107,
10
+ bin = 109,
11
+ array = 108,
12
+ endList = 106,
13
+ object = 116,
14
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./encode.js";
2
+ export * from "./decode.js";