~kiito/bare-js

1746c5aa62555c0f8b3b9644f60b021a08d303e3 — Emma 4 months ago 4b1ba72
Enums implemented, un-broke Structs
3 files changed, 106 insertions(+), 17 deletions(-)

M README.md
M example.js
M lib-bare.js
M README.md => README.md +9 -7
@@ 4,25 4,27 @@ This is a work-in-progress JavaScript/Node.js implementation of [BARE](https://b

The idea so far is, that the parser and converter will run with node.js, while the resulting JavaScript classes should be usable in any JavaScript environment.

Have a look at `examples.js` on how to create type definitions in code for now.  
Have a look at `examples.js` on how to create type definitions in code for now.

Or peek inside the `lib-bare.js`, where all conversion classes are located.
These are to be used directly when defining your own type.  
These are to be used directly when defining your own type.
Whenever a type takes some sort of arguments, they are static variables right at the top,
just make sure to set them correctly since there are, as of now, no integrity checks on them.

###What is still missing
 * The schema to js translator.
 * Union and Enum types.  
   These are difficult to translate to js, so I'm going to have to think about use cases and how to make them convenient to use
 * Union type, this one is difficult to translate to js, so I'm going to have to think about use cases and how to make them convenient to use
 * Conversion error handling and integrity verification.
 * Unit tests, these will come last.

###A note about Number and 64 bits
Javascript is a wonderful language, and as such it doesn't use an integer type for numbers.
Instead, every single number is stored as a double precision float.
This limits the usable range of integers to 53 bits, which means the maximum unsigned value is just over 9 quadrillion `(10^15)`.  
This limits the usable range of integers to 53 bits, which means the maximum unsigned value is just over 9 quadrillion `(10^15)`.

There is the [BitInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) type that allows arbitrary large integers.
But it has limitations; you can't use them in Math functions or beside regular Numbers.  
Keep this in mind when using the variable length, and the 64 bit integer types, since these return a BigInt by default.  
But it has limitations; you can't use them in Math functions or beside regular Numbers.
Keep this in mind when using the variable length, and the 64 bit integer types, since these return a BigInt by default.

The lib provides `safeNumber` which will convert a BigInt to Number if it fits into 53 bits, or throw an Error if it does not fit if you just need a little more headroom.


M example.js => example.js +42 -3
@@ 42,6 42,38 @@ let test2 = {
	'uint': 365555,
};

class ChannelEnum extends BARE.Enum {
	static keys = BARE.mapEnum(this, {
		0: 'RED',
		1: 'BLUE',
		2: 'GREEN',
		10: 'ALL',
	});
}

class Pair extends BARE.Struct {
	static entries = [
		['channel', ChannelEnum],
		['value', BARE.F64],
	];
}

class Test3 extends BARE.ArrayFixed {
	static length = 2;
	static type = Pair;
}

let test3 = [
	{
		channel: ChannelEnum.RED,
		value: 2 * Math.PI,
	},
	{
		channel: ChannelEnum.ALL,
		value: 6.9,
	},
];

class Address extends BARE.Struct {
	static entries = [
		['address', class extends BARE.ArrayFixed {


@@ 72,17 104,24 @@ let addrTest = Uint8Array.from([
	console.log(test1);
	let test1_bin = Test1.pack(test1);
	console.log(test1_bin);
	let test1_un = Test1.unpack(test1_bin);
	let [test1_un, t1l] = Test1.unpack(test1_bin);
	console.log(test1_un);

	console.log("-------------------");
	console.log(test2);
	let test2_bin = Test2.pack(test2);
	console.log(test2_bin);
	let test2_un = Test2.unpack(test2_bin);
	let [test2_un, t2l] = Test2.unpack(test2_bin);
	console.log(test2_un);

	console.log("-------------------");
	let addr = Address.unpack(addrTest);
	console.log(test3);
	let test3_bin = Test3.pack(test3);
	console.log(test3_bin);
	let [test3_un, t3l] = Test3.unpack(test3_bin);
	console.log(test3_un);

	console.log("-------------------");
	let [addr, al] = Address.unpack(addrTest);
	console.log(addr);
})();
\ No newline at end of file

M lib-bare.js => lib-bare.js +55 -7
@@ 19,6 19,16 @@ function twoWayMap(pairs) {
	return map;
}

function reverseMapping(obj) {
	let entries = Object.entries(obj);
	let reverse = {};
	for (let i = 0; i < entries.length; i++) {
		let [key, val] = entries[i];
		reverse[val] = key;
	}
	return reverse;
}

function safeNumber(bigInt) {
	if (bigInt > MAX_U53 || bigInt < -MAX_U53) {
		throw RangeError("BigInt value out of double precision range (53 bits)");


@@ 266,19 276,39 @@ class BareBool extends BarePrimitive {
	}
}

// TODO how (and where) to represent/store possible values for an enum (since js doesn't have an enum type)
function mapEnum(enumClass, keys) {
	let entries = Object.entries(keys);
	for (let i = 0; i < entries.length; i++) {
		let [key, name] = entries[i];
		enumClass[name] = key;
	}
	return keys;
}
class BareEnum extends BarePrimitive {
	static values; // = twoWayMap([['name', n], ...])
	// alternatively an array with gaps is allowed: ['ONE', 'TWO', , , 'FIVE']
	// this can also be done by index after the class definition:
	// Enum.keys[50] = 'SPECIAL';
	// although you then have to run mapEnum after that
	static keys; // = mapEnum(this, {0:'NAME1', 1:'NAME2', 4:'NAME5', ...})

	static pack(obj) {
		let num = this.values[obj];
	static pack(value) {
		if (!this.keys[value]) {
			throw ReferenceError("Invalid enum value");
		}
		let num = BigInt(value);
		return BareUInt.pack(num);
	}

	static unpack(raw) {
		let [value, bytes] = BareUInt.unpack(raw);
		let name = this.values[value];
		return [name, bytes];
		if (value > MAX_U32) {
			throw RangeError("Enum value out of range");
		}
		if (!this.keys[value]) {
			throw ReferenceError("Invalid enum value");
		}
		value = Number(value);
		return [value, bytes];
	}
}



@@ 362,6 392,10 @@ class BareOptional extends BareType {
	}

	static unpack(raw) {
		// make sure raw is a DataView, relevant if this is the top level element
		if (!raw instanceof DataView) {
			raw = new DataView(raw.buffer, raw.byteOffset);
		}
		let status = raw.getUint8(0);
		if (status === 0) {
			return [undefined, 1];


@@ 411,6 445,10 @@ class BareArray extends BareType {
	}

	static unpack(raw) {
		// make sure raw is a DataView, relevant if this is the top level element
		if (!raw instanceof DataView) {
			raw = new DataView(raw.buffer, raw.byteOffset);
		}
		let obj = [];
		let [numElements, length] = BareUInt.unpack(raw);
		if (numElements > MAX_ARRAY_LENGTH) {


@@ 445,6 483,10 @@ class BareMap extends BareType {
	}

	static unpack(raw) {
		// make sure raw is a DataView, relevant if this is the top level element
		if (!raw instanceof DataView) {
			raw = new DataView(raw.buffer, raw.byteOffset);
		}
		let obj = {};
		let [numEntries, length] = BareUInt.unpack(raw);
		if (numEntries > MAX_MAP_LENGTH) {


@@ 483,6 525,10 @@ class BareUnion extends BareType {
	}

	static unpack(raw) {
		// make sure raw is a DataView, relevant if this is the top level element
		if (!raw instanceof DataView) {
			raw = new DataView(raw.buffer, raw.byteOffset);
		}
		let [index, length] = BareUInt.unpack(raw);
		let objType = this.types[index];
		let [obj, bytes] = objType.unpack(new DataView(raw.buffer, raw.byteOffset + length));


@@ 514,12 560,14 @@ class BareStruct extends BareType {
			length += bytes;
			obj[key] = value;
		}
		return obj;
		return [obj, length];
	}
}

module.exports = {
	twoWayMap: twoWayMap,
	reverseMapping: reverseMapping,
	mapEnum: mapEnum,
	safeNumber: safeNumber,
	UInt: BareUInt,
	Int: BareInt,