@@ 1,80 @@
+/**
+ * This is the conversion script, to be run with node.js
+ *
+ * This script takes two optional arguments: [input, [output]]
+ * if either one is omitted it will use stdout and stdin respectively.
+ *
+ * I am using node.js 14.5.0 but as far as I know, this should run in earlier versions as well,
+ * as long as it supports promises and arrow syntax
+ */
+
+const fs = require('fs');
+
+function readFile(fileName) {
+ return new Promise((resolve, reject) => {
+ fs.readFile(fileName, {encoding: 'utf-8'}, function(err, data) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(data);
+ }
+ });
+ });
+}
+
+function readStdin() {
+ return new Promise((resolve, reject) => {
+ fs.read(/* stdin */0, function(err, bytesRead, buffer) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(buffer.toString("utf-8"));
+ }
+ });
+ });
+}
+
+function writeFile(fileName, data) {
+ return new Promise((resolve, reject) => {
+ fs.writeFile(fileName, data, function(err) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ })
+ });
+}
+
+function writeStdout(data) {
+ return new Promise((resolve, reject) => {
+ fs.write(/* stdout */ 1, data, function(err, written, string) {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+function parseSchema(schema) {
+ // TODO lexer, parser, conversion to js classes
+ return schema;
+}
+
+(async function() {
+ let [input, output] = process.argv.slice(2, 4);
+ let schema;
+ if (input) {
+ schema = await readFile(input);
+ } else {
+ schema = await readStdin();
+ }
+ let jsModule = parseSchema(schema);
+ if (output) {
+ await writeFile(jsModule);
+ } else {
+ await writeStdout(jsModule);
+ }
+})();<
\ No newline at end of file
@@ 1,54 @@
+let BARE = require("./lib-bare");
+
+class Point extends BARE.ArrayFixed {
+ static length = 3;
+ static type = BARE.F32;
+}
+
+class Test1 extends BARE.Struct {
+ static entries = [
+ ['str', BARE.String],
+ ['pos', Point],
+ ['verts', class extends BARE.Array {
+ static type = Point;
+ }],
+ ['test', BARE.F64],
+ ['flag', BARE.Bool],
+ ];
+}
+
+let test1 = {
+ str: "Hello World",
+ pos: [0, -3, 2.5],
+ verts: [
+ [-.5, 6900, 2e-6],
+ [Math.PI, Infinity, NaN],
+ ],
+ test: Math.PI,
+ flag: false,
+};
+
+class Test2 extends BARE.Struct {
+ static entries = [
+ ['vals', class extends BARE.Array {
+ static type = BARE.Int;
+ }],
+ ['uint', BARE.UInt],
+ ]
+}
+
+let test2 = {
+ 'vals': [420, -360, 0xFF_FFFF_FFFF],
+ 'uint': 365555,
+};
+
+(() => {
+
+ console.log(tst);
+
+ let res = Test2.pack(tst);
+ console.log(res);
+
+ let back = Test2.unpack(res);
+ console.log(back);
+})();<
\ No newline at end of file
@@ 1,511 @@
+"use strict";
+
+/* UTILS */
+
+function joinUint8Arrays(a, b) {
+ let c = new Uint8Array(a.length + b.length);
+ c.set(a, 0);
+ c.set(b, a.length);
+ return c;
+}
+
+function twoWayMap(pairs) {
+ let map = {};
+ for (let i = 0; i < pairs.length; i++) {
+ let [a, b] = pairs[i];
+ map[a] = b;
+ map[b] = a;
+ }
+ return map;
+}
+
+/* PRIMITIVE TYPES */
+
+class BareType {
+ /**
+ * pack(obj):
+ * <obj>: a js object compatible with the layout of the class this is called on
+ * returns: the binary representation of the class in a Uint8Array with values inserted from <obj>
+ */
+ static pack(obj) {}
+
+ /**
+ * unpack(raw):
+ * <raw>: a DataView on the Uint8Array of the message bytes, offset to the start of the unprocessed bytes
+ * returns: [an object with its values set according to the class layout, the number of bytes consumed by this operation]
+ */
+ static unpack(raw) {}
+}
+
+class BarePrimitive extends BareType {
+ static pack(value) {}
+}
+
+class BareUInt extends BarePrimitive {
+ static pack(value) {
+ let bytes = [];
+ while (value >= 0x80) {
+ bytes.push((value & 0xFF) | 0x80);
+ // shift 7 bits to the right (>> 7)
+ // js caps the value to 32bit when using binary operators, so I use division
+ value = Math.floor(value / 0x80);
+ }
+ bytes.push(value);
+ return Uint8Array.from(bytes);
+ }
+
+ static unpack(raw) {
+ let value = 0;
+ for (let i = 0;; i++) {
+ let byte = raw.getUint8(i);
+ if (byte < 0x80) {
+ value += byte * Math.pow(0x80, i);
+ return [value, i + 1];
+ }
+ // shifted i*7 bits to the left, same story
+ value += (byte & 0x7F) * Math.pow(0x80, i);
+ }
+ }
+}
+class BareInt extends BarePrimitive {
+ static pack(value) {
+ if (value < 0) {
+ value = (2 * Math.abs(value)) - 1;
+ } else {
+ value = 2 * value;
+ }
+ return BareUInt.pack(value);
+ }
+
+ static unpack(raw) {
+ let [value, length] = BareUInt.unpack(raw);
+ let sign = value % 2;
+ value = Math.floor(value / 2);
+ if (sign) {
+ value = -1 * (value + 1)
+ }
+ return [value, length];
+ }
+}
+
+class BareU8 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(1);
+ let view = new DataView(bin.buffer);
+ view.setUint8(0, value);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getUint8(0);
+ return [value, 1];
+ }
+}
+class BareU16 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(2);
+ let view = new DataView(bin.buffer);
+ view.setUint16(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getUint16(0, true);
+ return [value, 2];
+ }
+}
+class BareU32 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(4);
+ let view = new DataView(bin.buffer);
+ view.setUint32(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getUint32(0, true);
+ return [value, 4];
+ }
+}
+class BareU64 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(8);
+ let view = new DataView(bin.buffer);
+ view.setBigUint64(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getBigUint64(0, true);
+ return [value, 8];
+ }
+}
+
+class BareI8 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(1);
+ let view = new DataView(bin.buffer);
+ view.setInt8(0, value);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getInt8(0);
+ return [value, 1];
+ }
+}
+class BareI16 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(2);
+ let view = new DataView(bin.buffer);
+ view.setInt16(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getInt16(0, true);
+ return [value, 2];
+ }
+}
+class BareI32 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(4);
+ let view = new DataView(bin.buffer);
+ view.setInt32(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getInt32(0, true);
+ return [value, 4];
+ }
+}
+class BareI64 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(8);
+ let view = new DataView(bin.buffer);
+ view.setBigInt64(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getBigInt64(0, true);
+ return [value, 8];
+ }
+}
+
+class BareF32 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(4);
+ let view = new DataView(bin.buffer);
+ view.setFloat32(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getFloat32(0, true);
+ return [value, 4];
+ }
+}
+class BareF64 extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(8);
+ let view = new DataView(bin.buffer);
+ view.setFloat64(0, value, true);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getFloat64(0, true);
+ return [value, 8];
+ }
+}
+
+class BareBool extends BarePrimitive {
+ static pack(value) {
+ let bin = new Uint8Array(1);
+ let view = new DataView(bin.buffer);
+ value = value ? 1 : 0;
+ view.setUint8(0, value);
+ return bin;
+ }
+
+ static unpack(raw) {
+ let value = raw.getUint8(0);
+ value = !!value;
+ return [value, 1];
+ }
+}
+
+// TODO how (and where) to represent/store possible values for an enum (since js doesn't have an enum type)
+class BareEnum extends BarePrimitive {
+ static values; // = twoWayMap([['name', n], ...])
+
+ static pack(obj) {
+ let num = this.values[obj];
+ return BareUInt.pack(num);
+ }
+
+ static unpack(raw) {
+ let [value, bytes] = BareUInt.unpack(raw);
+ let name = this.values[value];
+ return [name, bytes];
+ }
+}
+
+const BareUTF8Encoder = new TextEncoder();
+const BareUTF8Decoder = new TextDecoder();
+
+class BareString extends BarePrimitive {
+ static pack(value) {
+ let bytes = BareUTF8Encoder.encode(value);
+ let length = BareUInt.pack(bytes.length);
+ return joinUint8Arrays(length, bytes);
+ }
+
+ static unpack(raw) {
+ let [length, lenBytes] = BareUInt.unpack(raw);
+ let bytes = new DataView(raw.buffer, raw.byteOffset + lenBytes, length);
+ let value = BareUTF8Decoder.decode(bytes);
+ return [value, length + lenBytes];
+ }
+}
+
+class BareDataFixed extends BarePrimitive {
+ static length;
+
+ static pack(value) {
+ return value;
+ }
+
+ static unpack(raw) {
+ let value = raw.buffer.slice(raw.byteOffset, raw.byteOffset + this.length);
+ return [value, this.length];
+ }
+}
+class BareData extends BarePrimitive {
+ static pack(value) {
+ let length = BareUInt.pack(value.length);
+ return joinUint8Arrays(length, value);
+ }
+
+ static unpack(raw) {
+ let [length, lenBytes] = BareUInt.unpack(raw);
+ let value = raw.buffer.slice(raw.byteOffset + lenBytes, raw.byteOffset + lenBytes + length);
+ return [value, length + lenBytes];
+ }
+}
+
+class BareVoid extends BarePrimitive {
+ // INVARIANT: may only be used as a member of sets in a tagged union
+ static pack(value) {
+ return new Uint8Array(0);
+ }
+
+ static unpack(raw) {
+ return [null, 0];
+ }
+}
+
+/* AGGREGATE TYPES */
+
+class BareOptional extends BareType {
+ static type;
+
+ static pack(obj) {
+ if (obj === undefined) {
+ return Uint8Array.of(0);
+ } else {
+ let bytes = this.type.pack(obj);
+ let bin = new Uint8Array(bytes.length + 1);
+ bin.set([1], 0);
+ bin.set(bytes, 1);
+ return bin;
+ }
+ }
+
+ static unpack(raw) {
+ let status = raw.getUint8(0);
+ if (status === 0) {
+ return [undefined, 1];
+ } else {
+ let [obj, bytes] = this.type.unpack(new DataView(raw.buffer, raw.byteOffset + 1));
+ return [obj, bytes + 1];
+ }
+ }
+}
+
+class BareArrayFixed extends BareType {
+ // INVARIANT: length is greater than 0
+ static length;
+ static type;
+
+ static pack(obj) {
+ let elements = new Uint8Array(0);
+ for (let i = 0; i < this.length; i++) {
+ let bytes = this.type.pack(obj[i]);
+ elements = joinUint8Arrays(elements, bytes);
+ }
+ return elements;
+ }
+
+ static unpack(raw) {
+ let obj = [];
+ let length = 0;
+ for (let i = 0; i < this.length; i++) {
+ let view = new DataView(raw.buffer, raw.byteOffset + length);
+ let [elem, bytes] = this.type.unpack(view);
+ obj.push(elem);
+ length += bytes;
+ }
+ return [obj, length];
+ }
+}
+class BareArray extends BareType {
+ static type;
+
+ static pack(obj) {
+ let bin = BareUInt.pack(obj.length);
+ for (let i = 0; i < obj.length; i++) {
+ let bytes = this.type.pack(obj[i]);
+ bin = joinUint8Arrays(bin, bytes);
+ }
+ return bin;
+ }
+
+ static unpack(raw) {
+ let obj = [];
+ let [numElements, length] = BareUInt.unpack(raw);
+ for (let i = 0; i < numElements; i++) {
+ let view = new DataView(raw.buffer, raw.byteOffset + length);
+ let [elem, bytes] = this.type.unpack(view);
+ length += bytes;
+ obj.push(elem);
+ }
+ return [obj, length];
+ }
+}
+
+class BareMap extends BareType {
+ // INVARIANT: map key is a primitive data type but not void, data or data<length>
+ static keyType;
+ static valueType;
+
+ static pack(obj) {
+ let keys = Object.keys(obj);
+ let bin = BareUInt.pack(keys.length);
+ for (let i = 0; i < keys.length; i++) {
+ let keyBytes = this.keyType.pack(keys[i]);
+ bin = joinUint8Arrays(bin, keyBytes);
+ let valueBytes = this.valueType.pack(obj[keys[i]]);
+ bin = joinUint8Arrays(bin, valueBytes);
+ }
+ return bin;
+ }
+
+ static unpack(raw) {
+ let obj = {};
+ let [numEntries, length] = BareUInt.unpack(raw);
+ for (let i = 0; i < numEntries; i++) {
+ let keyView = new DataView(raw.buffer, raw.byteOffset + length);
+ let [key, keyBytes] = this.type.unpack(keyView);
+ length += keyBytes;
+ let valueView = new DataView(raw.buffer, raw.byteOffset + length);
+ let [value, valueBytes] = this.valueType.unpack(valueView);
+ length += valueBytes;
+ obj[key] = value;
+ }
+ return [obj, length];
+ }
+}
+
+// TODO how do I store/represent the BARE type of an object for unions???
+class BareUnion extends BareType {
+ // INVARIANT: has at least one type
+ static types; // = twoWayMap([[class, i], ...])
+
+ static pack(obj) {
+ let unionIndex = this.types[obj.constructor];
+ if (!unionIndex) {
+ console.err("The union " + this + " does not support encoding the type " + obj)
+ }
+
+ let bin = obj.pack();
+ let index = BareUInt.pack(unionIndex);
+ return joinUint8Arrays(index, bin);
+ }
+
+ static unpack(raw) {
+ let [index, length] = BareUInt.unpack(raw);
+ let objType = this.types[index];
+ let [obj, bytes] = objType.unpack(new DataView(raw.buffer, raw.byteOffset + length));
+ return [obj, bytes + length];
+ }
+}
+
+class BareStruct extends BareType {
+ // INVARIANT: has at least one field
+ static entries; // = [['key', type], ...]
+
+ static pack(obj) {
+ let bin = new Uint8Array();
+ for (let i = 0; i < this.entries.length; i++) {
+ let [key, type] = this.entries[i];
+ let bytes = type.pack(obj[key]);
+ bin = joinUint8Arrays(bin, bytes);
+ }
+ return bin;
+ }
+
+ static unpack(raw) {
+ let obj = {};
+ let length = 0;
+ for (let i = 0; i < this.entries.length; i++) {
+ let [key, type] = this.entries[i];
+ let view = new DataView(raw.buffer, raw.byteOffset + length);
+ let [value, bytes] = type.unpack(view);
+ length += bytes;
+ obj[key] = value;
+ }
+ return obj;
+ }
+}
+
+module.exports = {
+ UInt: BareUInt,
+ Int: BareInt,
+ U8: BareU8,
+ U16: BareU16,
+ U32: BareU32,
+ U64: BareU64,
+ I8: BareI8,
+ I16: BareI16,
+ I32: BareI32,
+ I64: BareI64,
+ F32: BareF32,
+ F64: BareF64,
+ Bool: BareBool,
+ Enum: BareEnum, // TODO
+ String: BareString,
+ DataFixed: BareDataFixed,
+ Data: BareData,
+ Void: BareVoid,
+ Optional: BareOptional,
+ ArrayFixed: BareArrayFixed,
+ Array: BareArray,
+ Map: BareMap,
+ Union: BareUnion, // TODO
+ Struct: BareStruct,
+}
+
+/**
+ * Things to be aware of:
+ *
+ * JavaScript represents all numbers as double precision floating point,
+ * therefore the maximum precision for "integers" is clipped to 53-bit.
+ * (This means the maximum value is 0x1F_FFFF_FFFF_FFFF or about 9 quadrillion (10^15))
+ * This is an issue out of my scope, if you need that kind of precision,
+ * there are some libraries which add a proper 64-bit long type.
+ * The types UInt, Int, U64 and I64 will require some modification
+ * to make use of such a library.
+ */