mirror of
https://github.com/fluencelabs/assemblyscript-json
synced 2025-07-02 16:01:41 +00:00
Start with assemblyscript-bson as skeleton
This commit is contained in:
188
assembly/decoder.ts
Normal file
188
assembly/decoder.ts
Normal file
@ -0,0 +1,188 @@
|
||||
|
||||
|
||||
/**
|
||||
* Extend from this class to handle events from parser.
|
||||
* Default implementation traverses whole object tree and does nothing.
|
||||
*/
|
||||
export abstract class BSONHandler {
|
||||
setString(name: string, value: string): void {
|
||||
}
|
||||
|
||||
setBoolean(name: string, value: bool): void {
|
||||
}
|
||||
|
||||
setNull(name: string): void {
|
||||
}
|
||||
|
||||
setInteger(name: string, value: i32): void {
|
||||
}
|
||||
|
||||
setUint8Array(name: string, value: Uint8Array): void {
|
||||
}
|
||||
|
||||
pushArray(name: string): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
popArray(): void {
|
||||
}
|
||||
|
||||
pushObject(name: string): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
popObject(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend from this class to handle events from parser.
|
||||
* This implementation crashes on every unimplemented set/push method
|
||||
* to allow easier validation of input.
|
||||
*/
|
||||
export class ThrowingBSONHandler extends BSONHandler {
|
||||
setString(name: string, value: string): void {
|
||||
assert(false, 'Unexpected string field ' + name + ' : "' + value + '"');
|
||||
}
|
||||
|
||||
setBoolean(name: string, value: bool): void {
|
||||
assert(false, 'Unexpected boolean field ' + name + ' : ' + (value ? 'true' : 'false'));
|
||||
}
|
||||
|
||||
setNull(name: string): void {
|
||||
assert(false, 'Unexpected null field ' + name);
|
||||
}
|
||||
|
||||
setInteger(name: string, value: i32): void {
|
||||
let arr: Array<i32> = [value];
|
||||
assert(false, 'Unexpected integer field ' + name + ' : ' + arr.toString());
|
||||
}
|
||||
|
||||
setUint8Array(name: string, value: Uint8Array): void {
|
||||
assert(false, 'Unexpected byte array field ' + name + ' : ' + bin2str(value));
|
||||
}
|
||||
|
||||
pushArray(name: string): bool {
|
||||
assert(false, 'Unexpected array field' + name);
|
||||
return true;
|
||||
}
|
||||
|
||||
pushObject(name: string): bool {
|
||||
assert(false, 'Unexpected object field ' + name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class BSONDecoder<BSONHandlerT extends BSONHandler> {
|
||||
handler: BSONHandlerT;
|
||||
readIndex: i32 = 0;
|
||||
|
||||
constructor(handler: BSONHandlerT) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
deserialize(buffer: Uint8Array, startIndex: i32 = 0): void {
|
||||
this.readIndex = startIndex;
|
||||
|
||||
assert(buffer.length >= 5, "Document error: Size < 5 bytes");
|
||||
|
||||
let size : i32 = buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24;
|
||||
assert(size <= buffer.length, "Document error: Size mismatch");
|
||||
assert(buffer[buffer.length - 1] == 0x00, "Document error: Missing termination");
|
||||
|
||||
for (; ;) {
|
||||
// get element type
|
||||
let elementType = buffer[this.readIndex++]; // read type
|
||||
if (elementType === 0) break; // zero means last byte, exit
|
||||
|
||||
// get element name
|
||||
let end = this.readIndex;
|
||||
for (; buffer[end] !== 0x00 && end < buffer.length; end++);
|
||||
assert(end < buffer.length - 1, "Document error: Illegal key name");
|
||||
let name = bin2str(buffer.subarray(this.readIndex, end));
|
||||
this.readIndex = ++end; // skip terminating zero
|
||||
|
||||
switch (elementType) {
|
||||
case 0x02: // BSON type: String
|
||||
size = buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24;
|
||||
this.handler.setString(name, bin2str(buffer.subarray(this.readIndex, this.readIndex += size - 1)));
|
||||
this.readIndex++;
|
||||
break;
|
||||
|
||||
case 0x03: // BSON type: Document (Object)
|
||||
size = buffer[this.readIndex] | i32(buffer[this.readIndex + 1]) << 8 | i32(buffer[this.readIndex + 2]) << 16 | i32(buffer[this.readIndex + 3]) << 24;
|
||||
if (this.handler.pushObject(name)) {
|
||||
this.deserialize(buffer, this.readIndex);
|
||||
} else {
|
||||
this.readIndex += size;
|
||||
}
|
||||
this.handler.popObject();
|
||||
break;
|
||||
|
||||
case 0x04: // BSON type: Array
|
||||
size = buffer[this.readIndex] | i32(buffer[this.readIndex + 1]) << 8 | i32(buffer[this.readIndex + 2]) << 16 | i32(buffer[this.readIndex + 3]) << 24; // NO 'i' increment since the size bytes are reread during the recursion
|
||||
if (this.handler.pushArray(name)) {
|
||||
this.deserialize(buffer, this.readIndex);
|
||||
} else {
|
||||
this.readIndex += size;
|
||||
}
|
||||
this.handler.popArray();
|
||||
break;
|
||||
|
||||
case 0x05: // BSON type: Binary data
|
||||
size = buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24;
|
||||
if (buffer[this.readIndex++] === 0x04) {
|
||||
// BSON subtype: UUID (not supported)
|
||||
return
|
||||
}
|
||||
this.handler.setUint8Array(name, buffer.subarray(this.readIndex, this.readIndex += size)); // use slice() here to get a new array
|
||||
break;
|
||||
|
||||
case 0x08: // BSON type: Boolean
|
||||
this.handler.setBoolean(name, buffer[this.readIndex++] === 1);
|
||||
break;
|
||||
|
||||
case 0x0A: // BSON type: Null
|
||||
this.handler.setNull(name);
|
||||
break;
|
||||
|
||||
case 0x10: // BSON type: 32-bit integer
|
||||
this.handler.setInteger(name, buffer[this.readIndex++] | i32(buffer[this.readIndex++]) << 8 | i32(buffer[this.readIndex++]) << 16 | i32(buffer[this.readIndex++]) << 24);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false, "Parsing error: Unknown element");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse byte array as an UTF-8 string
|
||||
* @param {Uint8Array} bin UTF-8 text given as array of bytes
|
||||
* @return {String} UTF-8 Text string
|
||||
*/
|
||||
export function bin2str(bin: Uint8Array): string {
|
||||
let str = '', len = bin.length, i = 0;
|
||||
let c: i32, c2: i32, c3: i32;
|
||||
|
||||
while (i < len) {
|
||||
c = bin[i];
|
||||
if (c < 128) {
|
||||
str += String.fromCharCode(c);
|
||||
i++;
|
||||
}
|
||||
else if ((c > 191) && (c < 224)) {
|
||||
c2 = bin[i + 1];
|
||||
str += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
c2 = bin[i + 1];
|
||||
c3 = bin[i + 2];
|
||||
str += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
131
assembly/encoder.ts
Normal file
131
assembly/encoder.ts
Normal file
@ -0,0 +1,131 @@
|
||||
const START_SIZE = 32;
|
||||
// Growth should be aggressive as we don't free old buffer
|
||||
const GROWTH_MULT = 2;
|
||||
|
||||
declare function logStr(str: string): void;
|
||||
declare function logF64(val: f64): void;
|
||||
|
||||
export class BSONEncoder {
|
||||
offsets: Array<i32> = new Array<i32>();
|
||||
buffer: Uint8Array = new Uint8Array(START_SIZE)
|
||||
writeIndex: i32 = 4 // Make place for total size
|
||||
|
||||
serialize(): Uint8Array {
|
||||
this.writeByte(0);
|
||||
this.int32(this.writeIndex, 0);
|
||||
return this.buffer.subarray(0, this.writeIndex);
|
||||
}
|
||||
|
||||
setString(name: string, value: string): void {
|
||||
this.writeByte(0x02); // BSON type: String
|
||||
this.cstring(name);
|
||||
let startOffset = this.writeIndex;
|
||||
this.writeIndex += 4;
|
||||
this.cstring(value);
|
||||
this.int32(this.writeIndex - startOffset - 4, startOffset);
|
||||
}
|
||||
|
||||
setBoolean(name: string, value: bool): void {
|
||||
this.writeByte(0x08); // BSON type: Boolean
|
||||
this.cstring(name);
|
||||
this.writeByte(value ? 1 : 0);
|
||||
}
|
||||
|
||||
setNull(name: string): void {
|
||||
this.writeByte(0x0A); // BSON type: Null
|
||||
this.cstring(name);
|
||||
}
|
||||
|
||||
setInteger(name: string, value: i32): void {
|
||||
this.writeByte(0x10); // BSON type: int32
|
||||
this.cstring(name);
|
||||
this.int32(value);
|
||||
}
|
||||
|
||||
setUint8Array(name: string, value: Uint8Array): void {
|
||||
this.writeByte(0x05); // BSON type: Binary data
|
||||
this.cstring(name);
|
||||
this.int32(value.length);
|
||||
this.writeByte(0); // use generic binary subtype 0
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
this.writeByte(value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pushArray(name: string): void {
|
||||
this.writeByte(0x04); // BSON type: Array
|
||||
this.cstring(name);
|
||||
this.offsets.push(this.writeIndex);
|
||||
this.writeIndex += 4;
|
||||
}
|
||||
|
||||
popArray(): void {
|
||||
this.writeByte(0);
|
||||
let startOffset = this.offsets.pop();
|
||||
this.int32(this.writeIndex - startOffset, startOffset);
|
||||
}
|
||||
|
||||
pushObject(name: string): void {
|
||||
this.writeByte(0x03); // BSON type: Document
|
||||
this.cstring(name);
|
||||
this.offsets.push(this.writeIndex);
|
||||
this.writeIndex += 4;
|
||||
}
|
||||
|
||||
popObject(): void {
|
||||
this.writeByte(0);
|
||||
let startOffset = this.offsets.pop();
|
||||
this.int32(this.writeIndex - startOffset, startOffset);
|
||||
}
|
||||
|
||||
private cstring(str: string): void {
|
||||
// TODO: Handle newlines properly
|
||||
// str = str.replace(/\r\n/g, '\n');
|
||||
// TODO: Maybe use AssemblyScript std Unicode conversion?
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
let c = str.charCodeAt(i);
|
||||
if (c < 128) {
|
||||
this.writeByte(c);
|
||||
} else if (c < 2048) {
|
||||
this.writeByte((c >>> 6) | 192);
|
||||
this.writeByte((c & 63) | 128);
|
||||
} else {
|
||||
this.writeByte((c >>> 12) | 224);
|
||||
this.writeByte(((c >>> 6) & 63) | 128);
|
||||
this.writeByte((c & 63) | 128);
|
||||
}
|
||||
}
|
||||
this.writeByte(0);
|
||||
}
|
||||
|
||||
private int32(num: i32, offset: i32 = -1): void {
|
||||
if (offset == -1) {
|
||||
this.growIfNeeded(4);
|
||||
offset = this.writeIndex;
|
||||
this.writeIndex += 4;
|
||||
}
|
||||
this.buffer[offset] = (num) & 0xff;
|
||||
this.buffer[offset + 1] = (num >>> 8) & 0xff;
|
||||
this.buffer[offset + 2] = (num >>> 16) & 0xff;
|
||||
this.buffer[offset + 3] = (num >>> 24) & 0xff;
|
||||
}
|
||||
|
||||
private writeByte(b: u32): void {
|
||||
this.growIfNeeded(1);
|
||||
this.buffer[this.writeIndex++] = b;
|
||||
}
|
||||
|
||||
private growIfNeeded(numBytes: i32): void {
|
||||
if (this.buffer.length >= this.writeIndex + numBytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oldBuffer = this.buffer;
|
||||
this.buffer = new Uint8Array(this.buffer.length * GROWTH_MULT);
|
||||
for (let i = 0; i < oldBuffer.length; i++) {
|
||||
this.buffer[i] = oldBuffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
4
assembly/index.ts
Normal file
4
assembly/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { BSONDecoder } from './decoder';
|
||||
import { BSONEncoder } from './encoder';
|
||||
|
||||
export { BSONDecoder, BSONEncoder };
|
6
assembly/tsconfig.json
Normal file
6
assembly/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../../n/lib/node_modules/assemblyscript/std/assembly.json",
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user