Move JSON encoder from test to it’s proper place

This commit is contained in:
Vladimir Grichina 2019-01-06 18:26:05 -08:00
parent 5f3f5c809c
commit 791c1d7012
2 changed files with 90 additions and 223 deletions

View File

@ -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 logStr(str: string): void;
declare function logF64(val: f64): void; declare function logF64(val: f64): void;
export class BSONEncoder { export class JSONEncoder {
offsets: Array<i32> = new Array<i32>(); private isFirstKey: boolean = true
buffer: Uint8Array = new Uint8Array(START_SIZE) private inObject: Array<boolean> = [false]
writeIndex: i32 = 4 // Make place for total size private result: string = ""
serialize(): Uint8Array { serialize(): Uint8Array {
this.writeByte(0); // TODO: Write directly to UTF8 bytes
this.int32(this.writeIndex, 0); let utf8ptr = this.result.toUTF8();
return this.buffer.subarray(0, this.writeIndex); let buffer = new Uint8Array(this.result.lengthUTF8);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = load<u8>(utf8ptr + i);
}
return buffer.subarray(0, buffer.length - 1);
} }
setString(name: string, value: string): void { setString(name: string, value: string): void {
this.writeByte(0x02); // BSON type: String this.writeKey(name);
this.cstring(name); this.writeString(value);
let startOffset = this.writeIndex;
this.writeIndex += 4;
this.cstring(value);
this.int32(this.writeIndex - startOffset - 4, startOffset);
} }
setBoolean(name: string, value: bool): void { setBoolean(name: string, value: bool): void {
this.writeByte(0x08); // BSON type: Boolean this.writeKey(name);
this.cstring(name); this.writeBoolean(value);
this.writeByte(value ? 1 : 0);
} }
setNull(name: string): void { setNull(name: string): void {
this.writeByte(0x0A); // BSON type: Null this.writeKey(name);
this.cstring(name); this.write("null");
} }
setInteger(name: string, value: i32): void { setInteger(name: string, value: i32): void {
this.writeByte(0x10); // BSON type: int32 this.writeKey(name);
this.cstring(name); this.writeInteger(value);
this.int32(value);
} }
setUint8Array(name: string, value: Uint8Array): void { pushArray(name: string): bool {
this.writeByte(0x05); // BSON type: Binary data this.writeKey(name);
this.cstring(name); this.write("[");
this.int32(value.length); this.isFirstKey = true
this.writeByte(0); // use generic binary subtype 0 this.inObject.push(false);
for (let i = 0; i < value.length; i++) { return true;
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 { popArray(): void {
this.writeByte(0); this.write("]");
let startOffset = this.offsets.pop();
this.int32(this.writeIndex - startOffset, startOffset);
} }
pushObject(name: string): void { pushObject(name: string): bool {
this.writeByte(0x03); // BSON type: Document this.writeKey(name);
this.cstring(name); this.write("{");
this.offsets.push(this.writeIndex); this.isFirstKey = true
this.writeIndex += 4; this.inObject.push(true);
return true;
} }
popObject(): void { popObject(): void {
this.writeByte(0); this.write("}");
let startOffset = this.offsets.pop();
this.int32(this.writeIndex - startOffset, startOffset);
} }
private cstring(str: string): void { private writeKey(str: string): void {
// TODO: Handle newlines properly if (!this.isFirstKey ) {
// str = str.replace(/\r\n/g, '\n'); this.write(",");
// TODO: Maybe use AssemblyScript std Unicode conversion? } else {
for (let i = 0, len = str.length; i < len; i++) { this.isFirstKey = false;
let c = str.charCodeAt(i); }
if (c < 128) { if (str != null) {
this.writeByte(c); this.writeString(str);
} else if (c < 2048) { this.write(":");
this.writeByte((c >>> 6) | 192); }
this.writeByte((c & 63) | 128); }
} else {
this.writeByte((c >>> 12) | 224); private writeString(str: string): void {
this.writeByte(((c >>> 6) & 63) | 128); this.write('"');
this.writeByte((c & 63) | 128); 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 { private writeBoolean(value: bool): void {
if (offset == -1) { this.write(value ? "true" : "false");
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 { private writeInteger(value: i32): void {
this.growIfNeeded(1); // TODO: More efficient encoding
this.buffer[this.writeIndex++] = b; let arr: Array<i32> = [value];
this.write(arr.toString());
} }
private growIfNeeded(numBytes: i32): void { private write(str: string): void {
if (this.buffer.length >= this.writeIndex + numBytes) { this.result += str;
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];
}
} }
} }

View File

@ -1,126 +1,19 @@
import "allocator/arena"; 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 logStr(str: string): void;
declare function logF64(val: f64): void; declare function logF64(val: f64): void;
class JSONTestHandler extends JSONHandler {
isFirstKey: boolean = true
inObject: Array<boolean> = [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<i32> = [value];
this.write(arr.toString());
}
private write(str: string): void {
this.result += str;
}
}
export class StringConversionTests { export class StringConversionTests {
private static handler : JSONTestHandler = new JSONTestHandler(); private static handler : JSONEncoder = null;
static setUp(): void { static setUp(): void {
this.handler = new JSONTestHandler(); this.handler = new JSONEncoder();
} }
static createDecoder(): JSONDecoder<JSONTestHandler> { static createDecoder(): JSONDecoder<JSONEncoder> {
return new JSONDecoder(this.handler); return new JSONDecoder(this.handler);
} }
@ -208,24 +101,10 @@ export class StringConversionTests {
buffer[i] = load<u8>(utf8ptr + i); buffer[i] = load<u8>(utf8ptr + i);
} }
this.createDecoder().deserialize(buffer); this.createDecoder().deserialize(buffer);
assert(this.handler.result == expectedString, let resultBuffer = this.handler.serialize();
"Expected:\n" + expectedString + "\n" + "Actual:\n" + this.handler.result); let resultString = String.fromUTF8(resultBuffer.buffer.data, resultBuffer.length);
assert(resultString == expectedString,
"Expected:\n" + expectedString + "\n" + "Actual:\n" + resultString)
return true; return true;
} }
} }
function bytes2array(typedArr: Uint8Array): Array<u8> {
let arr = new Array<u8>();
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;
}