diff --git a/assembly/encoder.ts b/assembly/encoder.ts index 467e29f..8028f6d 100644 --- a/assembly/encoder.ts +++ b/assembly/encoder.ts @@ -1,131 +1,119 @@ -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 = new Array(); - buffer: Uint8Array = new Uint8Array(START_SIZE) - writeIndex: i32 = 4 // Make place for total size +export class JSONEncoder { + private isFirstKey: boolean = true + private inObject: Array = [false] + private result: string = "" serialize(): Uint8Array { - this.writeByte(0); - this.int32(this.writeIndex, 0); - return this.buffer.subarray(0, this.writeIndex); + // TODO: Write directly to UTF8 bytes + let utf8ptr = this.result.toUTF8(); + let buffer = new Uint8Array(this.result.lengthUTF8); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = load(utf8ptr + i); + } + return buffer.subarray(0, buffer.length - 1); } 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); + this.writeKey(name); + this.writeString(value); } setBoolean(name: string, value: bool): void { - this.writeByte(0x08); // BSON type: Boolean - this.cstring(name); - this.writeByte(value ? 1 : 0); + this.writeKey(name); + this.writeBoolean(value); } setNull(name: string): void { - this.writeByte(0x0A); // BSON type: Null - this.cstring(name); + this.writeKey(name); + this.write("null"); } setInteger(name: string, value: i32): void { - this.writeByte(0x10); // BSON type: int32 - this.cstring(name); - this.int32(value); + this.writeKey(name); + this.writeInteger(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; + pushArray(name: string): bool { + this.writeKey(name); + this.write("["); + this.isFirstKey = true + this.inObject.push(false); + return true; } popArray(): void { - this.writeByte(0); - let startOffset = this.offsets.pop(); - this.int32(this.writeIndex - startOffset, startOffset); + this.write("]"); } - pushObject(name: string): void { - this.writeByte(0x03); // BSON type: Document - this.cstring(name); - this.offsets.push(this.writeIndex); - this.writeIndex += 4; + pushObject(name: string): bool { + this.writeKey(name); + this.write("{"); + this.isFirstKey = true + this.inObject.push(true); + return true; } popObject(): void { - this.writeByte(0); - let startOffset = this.offsets.pop(); - this.int32(this.writeIndex - startOffset, startOffset); + this.write("}"); } - 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); + private writeKey(str: string): void { + if (!this.isFirstKey ) { + this.write(","); + } else { + this.isFirstKey = false; + } + if (str != null) { + this.writeString(str); + this.write(":"); + } + } + + private writeString(str: string): void { + this.write('"'); + let savedIndex = 0; + for (let i = 0; i < str.length; i++) { + let char = str.charCodeAt(i); + let needsEscaping = char < 0x20 || char == '"'.charCodeAt(0) || char == '\\'.charCodeAt(0); + if (needsEscaping) { + this.write(str.substring(savedIndex, i)); + savedIndex = i + 1; + if (char == '"'.charCodeAt(0)) { + this.write('\\"'); + } else if (char == "\\".charCodeAt(0)) { + this.write("\\\\"); + } else if (char == "\b".charCodeAt(0)) { + this.write("\\b"); + } else if (char == "\n".charCodeAt(0)) { + this.write("\\n"); + } else if (char == "\r".charCodeAt(0)) { + this.write("\\r"); + } else if (char == "\t".charCodeAt(0)) { + this.write("\\t"); + } else { + // TODO: Implement encoding for other contol characters + assert(false, "Unsupported control chracter"); + } } } - this.writeByte(0); + this.write(str.substring(savedIndex, str.length)); + this.write('"'); } - 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 writeBoolean(value: bool): void { + this.write(value ? "true" : "false"); } - private writeByte(b: u32): void { - this.growIfNeeded(1); - this.buffer[this.writeIndex++] = b; + private writeInteger(value: i32): void { + // TODO: More efficient encoding + let arr: Array = [value]; + this.write(arr.toString()); } - 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]; - } + private write(str: string): void { + this.result += str; } -} - - +} \ No newline at end of file diff --git a/tests/assembly/roundtrip.spec.as.ts b/tests/assembly/roundtrip.spec.as.ts index 5a0d221..a7e5f45 100644 --- a/tests/assembly/roundtrip.spec.as.ts +++ b/tests/assembly/roundtrip.spec.as.ts @@ -1,126 +1,19 @@ import "allocator/arena"; -import { JSONDecoder, JSONHandler } from "../../assembly/decoder"; +import { JSONDecoder } from "../../assembly/decoder"; +import { JSONEncoder } from "../../assembly/encoder"; declare function logStr(str: string): void; declare function logF64(val: f64): void; -class JSONTestHandler extends JSONHandler { - isFirstKey: boolean = true - inObject: Array = [false] - result: string = "" - - setString(name: string, value: string): void { - this.writeKey(name); - this.writeString(value); - } - - setBoolean(name: string, value: bool): void { - this.writeKey(name); - this.writeBoolean(value); - } - - setNull(name: string): void { - this.writeKey(name); - this.write("null"); - } - - setInteger(name: string, value: i32): void { - this.writeKey(name); - this.writeInteger(value); - } - - pushArray(name: string): bool { - this.writeKey(name); - this.write("["); - this.isFirstKey = true - this.inObject.push(false); - return true; - } - - popArray(): void { - this.write("]"); - } - - pushObject(name: string): bool { - this.writeKey(name); - this.write("{"); - this.isFirstKey = true - this.inObject.push(true); - return true; - } - - popObject(): void { - this.write("}"); - } - - private writeKey(str: string): void { - if (!this.isFirstKey ) { - this.write(","); - } else { - this.isFirstKey = false; - } - if (str != null) { - this.writeString(str); - this.write(":"); - } - } - - private writeString(str: string): void { - this.write('"'); - let savedIndex = 0; - for (let i = 0; i < str.length; i++) { - let char = str.charCodeAt(i); - let needsEscaping = char < 0x20 || char == '"'.charCodeAt(0) || char == '\\'.charCodeAt(0); - if (needsEscaping) { - this.write(str.substring(savedIndex, i)); - savedIndex = i + 1; - if (char == '"'.charCodeAt(0)) { - this.write('\\"'); - } else if (char == "\\".charCodeAt(0)) { - this.write("\\\\"); - } else if (char == "\b".charCodeAt(0)) { - this.write("\\b"); - } else if (char == "\n".charCodeAt(0)) { - this.write("\\n"); - } else if (char == "\r".charCodeAt(0)) { - this.write("\\r"); - } else if (char == "\t".charCodeAt(0)) { - this.write("\\t"); - } else { - // TODO: Implement encoding for other contol characters - assert(false, "Unsupported control chracter"); - } - } - } - this.write(str.substring(savedIndex, str.length)); - this.write('"'); - } - - private writeBoolean(value: bool): void { - this.write(value ? "true" : "false"); - } - - private writeInteger(value: i32): void { - // TODO: More efficient encoding - let arr: Array = [value]; - this.write(arr.toString()); - } - - private write(str: string): void { - this.result += str; - } -} - - export class StringConversionTests { - private static handler : JSONTestHandler = new JSONTestHandler(); + private static handler : JSONEncoder = null; static setUp(): void { - this.handler = new JSONTestHandler(); + this.handler = new JSONEncoder(); } - static createDecoder(): JSONDecoder { + static createDecoder(): JSONDecoder { return new JSONDecoder(this.handler); } @@ -208,24 +101,10 @@ export class StringConversionTests { buffer[i] = load(utf8ptr + i); } this.createDecoder().deserialize(buffer); - assert(this.handler.result == expectedString, - "Expected:\n" + expectedString + "\n" + "Actual:\n" + this.handler.result); + let resultBuffer = this.handler.serialize(); + let resultString = String.fromUTF8(resultBuffer.buffer.data, resultBuffer.length); + assert(resultString == expectedString, + "Expected:\n" + expectedString + "\n" + "Actual:\n" + resultString) return true; } -} - -function bytes2array(typedArr: Uint8Array): Array { - let arr = new Array(); - for (let i = 0; i < typedArr.length; i++) { - arr.push(typedArr[i]); - } - return arr; -} - -function hex2bin(hex: string): Uint8Array { - let bin = new Uint8Array(hex.length >>> 1); - for (let i = 0, len = hex.length >>> 1; i < len; i++) { - bin[i] = u32(parseInt(hex.substr(i << 1, 2), 16)); - } - return bin; } \ No newline at end of file