mirror of
https://github.com/fluencelabs/assemblyscript-json
synced 2025-04-24 22:32:20 +00:00
Start with assemblyscript-bson as skeleton
This commit is contained in:
commit
cdc035253f
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Marco Paland
|
||||
Copyright (c) 2018 NEAR Protocol
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
108
README.md
Normal file
108
README.md
Normal file
@ -0,0 +1,108 @@
|
||||
# assemblyscript-bson
|
||||
|
||||
BSON encoder / decoder for AssemblyScript somewhat based on https://github.com/mpaland/bsonfy.
|
||||
|
||||
Special thanks to https://github.com/MaxGraey/bignum.wasm for basic unit testing infra for AssemblyScript.
|
||||
|
||||
# Limitations
|
||||
|
||||
This is developed for use in smart contracts written in AssemblyScript for https://github.com/nearprotocol/nearcore.
|
||||
This imposes such limitations:
|
||||
- Only limited data types are supported:
|
||||
- arrays
|
||||
- objects
|
||||
- 32-bit integers
|
||||
- strings
|
||||
- booleans
|
||||
- null
|
||||
- `Uint8Array`
|
||||
- We assume that memory never needs to be deallocated (cause these contracts are short-lived).
|
||||
|
||||
Note that this mostly just defines the way it's currently implemented. Contributors are welcome to fix limitations.
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
## Encoding BSON
|
||||
|
||||
```ts
|
||||
// Make sure memory allocator is available
|
||||
import "allocator/arena";
|
||||
// Import encoder
|
||||
import { BSONEncoder } from "path/to/module";
|
||||
|
||||
// Create encoder
|
||||
let encoder = new BSONEncoder();
|
||||
|
||||
// Construct necessary object
|
||||
encoder.pushObject("obj");
|
||||
encoder.setInteger("int", 10);
|
||||
encoder.setString("str", "");
|
||||
encoder.popObject();
|
||||
|
||||
// Get serialized data
|
||||
let bson: Uint8Array = encoder.serialize();
|
||||
|
||||
```
|
||||
|
||||
## Parsing BSON
|
||||
|
||||
```ts
|
||||
// Make sure memory allocator is available
|
||||
import "allocator/arena";
|
||||
// Import decoder
|
||||
import { BSONDecoder, BSONHandler } from "path/to/module";
|
||||
|
||||
// Events need to be received by custom object extending BSONHandler.
|
||||
// NOTE: All methods are optional to implement.
|
||||
class MyBSONEventsHandler extends BSONHandler {
|
||||
setString(name: string, value: string): void {
|
||||
// Handle field
|
||||
}
|
||||
|
||||
setBoolean(name: string, value: bool): void {
|
||||
// Handle field
|
||||
}
|
||||
|
||||
setNull(name: string): void {
|
||||
// Handle field
|
||||
}
|
||||
|
||||
setInteger(name: string, value: i32): void {
|
||||
// Handle field
|
||||
}
|
||||
|
||||
setUint8Array(name: string, value: Uint8Array): void {
|
||||
// Handle field
|
||||
}
|
||||
|
||||
pushArray(name: string): bool {
|
||||
// Handle array start
|
||||
return true; // true means that nested object needs to be traversed, false otherwise
|
||||
}
|
||||
|
||||
popArray(): void {
|
||||
// Handle array end
|
||||
}
|
||||
|
||||
pushObject(name: string): bool {
|
||||
// Handle object start
|
||||
return true; // true means that nested object needs to be traversed, false otherwise
|
||||
}
|
||||
|
||||
popObject(): void {
|
||||
// Handle object end
|
||||
}
|
||||
}
|
||||
|
||||
// Create decoder
|
||||
let decoder = new BSONDecoder<MyBSONEventsHandler>(new MyBSONEventsHandler());
|
||||
|
||||
// Let's assume BSON data is available in this variable
|
||||
let bson: Uint8Array = ...;
|
||||
|
||||
// Parse BSON
|
||||
decoder.deserialize(bson); // This will send events to MyBSONEventsHandler
|
||||
|
||||
```
|
||||
|
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"
|
||||
]
|
||||
}
|
8
index.js
Normal file
8
index.js
Normal file
@ -0,0 +1,8 @@
|
||||
const fs = require("fs");
|
||||
const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm"));
|
||||
const imports = {
|
||||
//log:
|
||||
};
|
||||
Object.defineProperty(module, "exports", {
|
||||
get: () => new WebAssembly.Instance(compiled, imports).exports
|
||||
});
|
4859
package-lock.json
generated
Normal file
4859
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"scripts": {
|
||||
"asbuild:untouched": "npx asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
|
||||
"asbuild:optimized": "npx asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
|
||||
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
|
||||
"asbuild:test": "npm run asbuild:test:encoder && npm run asbuild:test:decoder",
|
||||
"asbuild:test:encoder": "npx asc tests/assembly/encoder.spec.as.ts -b tests/build/encoder.wasm -t tests/build/encoder.wat --validate --sourceMap --importMemory --debug",
|
||||
"asbuild:test:decoder": "npx asc tests/assembly/decoder.spec.as.ts -b tests/build/decoder.wasm -t tests/build/decoder.wat --validate --sourceMap --importMemory --debug",
|
||||
"test": "npm run asbuild:test && ava -v --serial",
|
||||
"test:ci": "npm run asbuild:test && ava --fail-fast --serial"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^10.12.3",
|
||||
"assemblyscript": "AssemblyScript/assemblyscript",
|
||||
"ava": "1.0.0-rc.1",
|
||||
"ts-node": "^7.0.1",
|
||||
"typedoc": "^0.13.0",
|
||||
"typescript": "^3.1.6"
|
||||
},
|
||||
"ava": {
|
||||
"compileEnhancements": true,
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register/transpile-only"
|
||||
],
|
||||
"files": [
|
||||
"tests/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
}
|
272
tests/assembly/decoder.spec.as.ts
Normal file
272
tests/assembly/decoder.spec.as.ts
Normal file
@ -0,0 +1,272 @@
|
||||
import "allocator/arena";
|
||||
|
||||
import { BSONDecoder, BSONHandler } from "../../assembly/decoder";
|
||||
|
||||
declare function logStr(str: string): void;
|
||||
declare function logF64(val: f64): void;
|
||||
|
||||
/*
|
||||
let deserialize_vector = [
|
||||
{
|
||||
obj: { "BSON": ["awesome", 5.05, 1986] },
|
||||
bson: "310000000442534f4e002600000002300008000000617765736f6d65000131003333333333331440103200c20700000000",
|
||||
},
|
||||
{
|
||||
obj: { arr: ["foo", "bar", 100, 1000], ta: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), obj: { int32: 10, int64: 1125899906842624, flo: 3.141592653 } },
|
||||
bson: "7500000004617272002900000002300004000000666f6f00023100040000006261720010320064000000103300e8030000000574610008000000000102030405060708036f626a002c00000010696e743332000a00000012696e74363400000000000000040001666c6f0038e92f54fb2109400000"
|
||||
},
|
||||
{
|
||||
obj: { id: 123456, sk: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), pk: new Uint8Array([255, 254, 253, 252, 251, 250, 249, 248]) },
|
||||
bson: "2f0000001069640040e2010005736b000800000000010203040506070805706b000800000000fffefdfcfbfaf9f800"
|
||||
},
|
||||
];
|
||||
*/
|
||||
enum EventType {
|
||||
String = 1,
|
||||
Bytes = 2,
|
||||
Int = 3,
|
||||
Bool = 4,
|
||||
Null = 5,
|
||||
PushArray = 6,
|
||||
PopArray = 7,
|
||||
PushObject = 8,
|
||||
PopObject = 9
|
||||
}
|
||||
|
||||
class BSONEvent {
|
||||
constructor(public type: EventType, public name: string, public valuePtr: usize) { }
|
||||
|
||||
getValue<T>() : T {
|
||||
return changetype<T>(this.valuePtr);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
switch (this.type) {
|
||||
case EventType.String:
|
||||
return this.name + ": " + "'" + this.getValue<string>() + "'";
|
||||
case EventType.Int:
|
||||
// TODO: Should be some easy way to convert int to string
|
||||
let intArray = new Array<i32>();
|
||||
intArray.push(this.getValue<i32>());
|
||||
return this.name + ": " + intArray.toString();
|
||||
case EventType.Bool:
|
||||
let value = this.getValue<bool>();
|
||||
return this.name + ": " + (value ? "true" : "false");
|
||||
case EventType.Null:
|
||||
return this.name + ": null";
|
||||
case EventType.PushArray:
|
||||
return this.name + ": [";
|
||||
case EventType.PopArray:
|
||||
return "]";
|
||||
case EventType.PushObject:
|
||||
return this.name + ": {";
|
||||
case EventType.PopObject:
|
||||
return "}";
|
||||
case EventType.Bytes:
|
||||
return this.name + ": " + bytes2array(this.getValue<Uint8Array>()).toString();
|
||||
default:
|
||||
return "<Invalid BSONEvent>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BSONTestHandler extends BSONHandler {
|
||||
events: Array<BSONEvent> = new Array<BSONEvent>();
|
||||
|
||||
setString(name: string, value: string): void {
|
||||
this.events.push(new BSONEvent(EventType.String, name, changetype<usize>(value)));
|
||||
}
|
||||
|
||||
setBoolean(name: string, value: bool): void {
|
||||
this.events.push(new BSONEvent(EventType.Bool, name, changetype<usize>(value)));
|
||||
}
|
||||
|
||||
setNull(name: string): void {
|
||||
this.events.push(new BSONEvent(EventType.Null, name, 0));
|
||||
}
|
||||
|
||||
setInteger(name: string, value: i32): void {
|
||||
this.events.push(new BSONEvent(EventType.Int, name, changetype<usize>(value)));
|
||||
}
|
||||
|
||||
setUint8Array(name: string, value: Uint8Array): void {
|
||||
this.events.push(new BSONEvent(EventType.Bytes, name, changetype<usize>(value)));
|
||||
}
|
||||
|
||||
pushArray(name: string): bool {
|
||||
this.events.push(new BSONEvent(EventType.PushArray, name, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
popArray(): void {
|
||||
this.events.push(new BSONEvent(EventType.PopArray, "", 0));
|
||||
}
|
||||
|
||||
pushObject(name: string): bool {
|
||||
this.events.push(new BSONEvent(EventType.PushObject, name, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
popObject(): void {
|
||||
this.events.push(new BSONEvent(EventType.PopObject, "", 0));
|
||||
}
|
||||
}
|
||||
|
||||
let handler : BSONTestHandler = new BSONTestHandler();
|
||||
|
||||
export class StringConversionTests {
|
||||
|
||||
static setUp(): void {
|
||||
handler.events = new Array<BSONEvent>();
|
||||
}
|
||||
|
||||
static createDecoder(): BSONDecoder<BSONTestHandler> {
|
||||
return new BSONDecoder(handler);
|
||||
}
|
||||
|
||||
static shouldHandleEmptyObject(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("0500000000"));
|
||||
return handler.events.length == 0
|
||||
}
|
||||
|
||||
static shouldHandleInt32(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("0e00000010696e74003412000000"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "int: 4660"; // 0x1234
|
||||
}
|
||||
|
||||
static shouldHandleNegativeInt32(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("0e00000010696e7400f6ffffff00"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "int: -10";
|
||||
}
|
||||
|
||||
static shouldHandleString(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("1a00000002737472000c00000048656c6c6f20576f726c640000"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "str: 'Hello World'";
|
||||
}
|
||||
|
||||
static shouldHandleUTF8String() : bool {
|
||||
this.createDecoder().deserialize(hex2bin("17000000027374720009000000c384c396c39cc39f0000"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "str: '" + "\u00C4\u00D6\u00DC\u00DF" + "'";
|
||||
}
|
||||
|
||||
static shouldHandleBooleanFalse(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("0c00000008626f6f6c000000"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "bool: false";
|
||||
}
|
||||
|
||||
static shouldHandleBooleanTrue(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("0c00000008626f6f6c000100"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "bool: true";
|
||||
}
|
||||
|
||||
static shouldHandleNull(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("0a0000000a6e756c0000"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "nul: null";
|
||||
}
|
||||
|
||||
static shouldHandleBytes(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("190000000562696e000a00000000010203040506070809ff00"));
|
||||
return handler.events.length == 1 &&
|
||||
handler.events[0].toString() == "bin: 1,2,3,4,5,6,7,8,9,255";
|
||||
};
|
||||
|
||||
static shouldHandleArray(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("2b000000046172720021000000103000fa000000103100fb000000103200fc000000103300fd0000000000"));
|
||||
return handler.events.length == 6 &&
|
||||
handler.events[0].toString() == "arr: [" &&
|
||||
handler.events[1].toString() == "0: 250" && // 0xFA
|
||||
handler.events[2].toString() == "1: 251" && // 0XFB
|
||||
handler.events[3].toString() == "2: 252" && // 0xFC
|
||||
handler.events[4].toString() == "3: 253" && // 0xFD
|
||||
handler.events[5].toString() == "]";
|
||||
};
|
||||
|
||||
static shouldHandleNestedArray(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("4f000000046172720045000000043000210000001030001000000010310011000000103200120000001033001300000000103100fa000000103200fb000000103300fc000000103400fd0000000000"));
|
||||
return handler.events.length == 12 &&
|
||||
handler.events[0].toString() == "arr: [" &&
|
||||
handler.events[1].toString() == "0: [" &&
|
||||
handler.events[2].toString() == "0: 16" && // 0x10
|
||||
handler.events[3].toString() == "1: 17" && // 0X11
|
||||
handler.events[4].toString() == "2: 18" && // 0x12
|
||||
handler.events[5].toString() == "3: 19" && // 0x13
|
||||
handler.events[6].toString() == "]" &&
|
||||
handler.events[7].toString() == "1: 250" && // 0xFA
|
||||
handler.events[8].toString() == "2: 251" && // 0XFB
|
||||
handler.events[9].toString() == "3: 252" && // 0xFC
|
||||
handler.events[10].toString() == "4: 253" && // 0xFD
|
||||
handler.events[11].toString() == "]";
|
||||
}
|
||||
|
||||
static shouldHandleObjects(): bool {
|
||||
this.createDecoder().deserialize(hex2bin("22000000036f626a001800000010696e74000a000000027374720001000000000000"));
|
||||
return handler.events.length == 4 &&
|
||||
handler.events[0].toString() == "obj: {" &&
|
||||
handler.events[1].toString() == "int: 10" &&
|
||||
handler.events[2].toString() == "str: ''" &&
|
||||
handler.events[3].toString() == "}";
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Enable when serializer is ready
|
||||
static shouldHandleComplexObjects(): bool {
|
||||
for (let i = 0; i < deserialize_vector.length; i++) {
|
||||
let bson = BSON.serialize(deserialize_vector[i].obj);
|
||||
this.createDecoder().deserialize(hex2bin(deserialize_vector[i].bson), true);
|
||||
expect(obj).to.deep.equal(deserialize_vector[i].obj);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static shouldAbortDocumentTooSmall(): void {
|
||||
this.createDecoder().deserialize(hex2bin("04000000"));
|
||||
}
|
||||
|
||||
static shouldAbortDocumentTermination1(): void {
|
||||
this.createDecoder().deserialize(hex2bin("0c00000008626f6f6c000001"));
|
||||
}
|
||||
static shouldAbortDocumentTermination2(): void {
|
||||
this.createDecoder().deserialize(hex2bin("0c00000008626f6f6c0000"));
|
||||
}
|
||||
|
||||
static shouldAbortDocumentSizeMismatch(): void {
|
||||
this.createDecoder().deserialize(hex2bin("0d00000008626f6f6c000000"));
|
||||
}
|
||||
|
||||
static shouldAbortIllegalKeyname(): void {
|
||||
this.createDecoder().deserialize(hex2bin("0c00000008626f6f6c010100"));
|
||||
}
|
||||
|
||||
static shouldAbortUnknownElement(): void {
|
||||
this.createDecoder().deserialize(hex2bin("0c00000018626f6f6c000000"));
|
||||
}
|
||||
}
|
||||
|
||||
function logEvents(): void {
|
||||
for (let i = 0; i < handler.events.length; i++) {
|
||||
logStr("events:" + handler.events[i].toString());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
127
tests/assembly/encoder.spec.as.ts
Normal file
127
tests/assembly/encoder.spec.as.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import "allocator/arena";
|
||||
|
||||
import { BSONEncoder } from "../../assembly/encoder";
|
||||
|
||||
declare function logStr(str: string): void;
|
||||
declare function logF64(val: f64): void;
|
||||
|
||||
let encoder : BSONEncoder;
|
||||
|
||||
export class StringConversionTests {
|
||||
|
||||
static setUp(): void {
|
||||
encoder = new BSONEncoder();
|
||||
}
|
||||
|
||||
static shouldHandleEmptyObject(): bool {
|
||||
return encodedMatches("0500000000");
|
||||
}
|
||||
|
||||
static shouldHandleInt32(): bool {
|
||||
encoder.setInteger("int", 0x1234);
|
||||
return encodedMatches("0e00000010696e74003412000000");
|
||||
}
|
||||
|
||||
static shouldHandleNegativeInt32(): bool {
|
||||
encoder.setInteger("int", -10);
|
||||
return encodedMatches("0e00000010696e7400f6ffffff00");
|
||||
}
|
||||
|
||||
static shouldHandleString(): bool {
|
||||
encoder.setString("str", "Hello World");
|
||||
return encodedMatches("1a00000002737472000c00000048656c6c6f20576f726c640000");
|
||||
}
|
||||
|
||||
static shouldHandleUnicodeString(): bool {
|
||||
encoder.setString("str", "\u00C4\u00D6\u00DC\u00DF");
|
||||
return encodedMatches("17000000027374720009000000c384c396c39cc39f0000");
|
||||
}
|
||||
|
||||
static shouldHandleBoolFalse(): bool {
|
||||
encoder.setBoolean("bool", false);
|
||||
return encodedMatches("0c00000008626f6f6c000000");
|
||||
}
|
||||
|
||||
static shouldHandleBoolTrue(): bool {
|
||||
encoder.setBoolean("bool", true);
|
||||
return encodedMatches("0c00000008626f6f6c000100");
|
||||
}
|
||||
|
||||
static shouldHandleNull(): bool {
|
||||
encoder.setNull("nul");
|
||||
return encodedMatches("0a0000000a6e756c0000");
|
||||
}
|
||||
|
||||
static shouldHandleBinary(): bool {
|
||||
encoder.setUint8Array("bin", array2bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 0xFF]));
|
||||
return encodedMatches("190000000562696e000a00000000010203040506070809ff00");
|
||||
}
|
||||
|
||||
static shouldHandleArray(): bool {
|
||||
let arr : Array<i32> = [0xFA, 0xFB, 0xFC, 0xFD];
|
||||
encoder.pushArray("arr");
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
encoder.setInteger(itoa(i), arr[i]);
|
||||
}
|
||||
encoder.popArray();
|
||||
return encodedMatches("2b000000046172720021000000103000fa000000103100fb000000103200fc000000103300fd0000000000");
|
||||
}
|
||||
|
||||
static shouldHandleNestedArray(): bool {
|
||||
encoder.pushArray("arr");
|
||||
encoder.pushArray("0");
|
||||
let innerArray : Array<i32> = [0x10, 0x11, 0x12, 0x13];
|
||||
for (let i = 0; i < innerArray.length; i++) {
|
||||
encoder.setInteger(itoa(i), innerArray[i]);
|
||||
}
|
||||
encoder.popArray();
|
||||
let outerArray : Array<i32> = [0xFA, 0xFB, 0xFC, 0xFD];
|
||||
for (let i = 0; i < outerArray.length; i++) {
|
||||
encoder.setInteger(itoa(i + 1), outerArray[i]);
|
||||
}
|
||||
encoder.popArray();
|
||||
return encodedMatches("4f000000046172720045000000043000210000001030001000000010310011000000103200120000001033001300000000103100fa000000103200fb000000103300fc000000103400fd0000000000");
|
||||
}
|
||||
|
||||
static shouldHandleObject(): bool {
|
||||
encoder.pushObject("obj");
|
||||
encoder.setInteger("int", 10);
|
||||
encoder.setString("str", "");
|
||||
encoder.popObject();
|
||||
return encodedMatches("22000000036f626a001800000010696e74000a000000027374720001000000000000");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Expose this from std instead of this ugly hack
|
||||
function itoa(i: i32): string {
|
||||
let arr: Array<i32> = [i];
|
||||
return arr.toString();
|
||||
}
|
||||
|
||||
function encodedMatches(hexStr: String): bool {
|
||||
let bson = encoder.serialize();
|
||||
let asHex = bin2hex(bson);
|
||||
let result = asHex == hexStr;
|
||||
if (!result) {
|
||||
logStr("expected: " + hexStr);
|
||||
logStr("actual: " + asHex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function bin2hex(bin: Uint8Array, uppercase: boolean = false): string {
|
||||
let hex = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
let str = "";
|
||||
for (let i = 0, len = bin.length; i < len; i++) {
|
||||
str += hex.charAt((bin[i] >>> 4) & 0x0f) + hex.charAt(bin[i] & 0x0f);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function array2bytes(arr: Array<u8>): Uint8Array {
|
||||
let bytes = new Uint8Array(arr.length);
|
||||
for (let i: i32 = 0; i < arr.length; i++) {
|
||||
bytes[i] = arr[i];
|
||||
}
|
||||
return bytes;
|
||||
}
|
63
tests/assembly/tsconfig.json
Normal file
63
tests/assembly/tsconfig.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
},
|
||||
"extends": "../../node_modules/assemblyscript/std/assembly.json",
|
||||
"include": [
|
||||
"./**/*.ts","*.ts"
|
||||
]
|
||||
}
|
3
tests/decoder.spec.ts
Normal file
3
tests/decoder.spec.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineTestsFromModule } from './utils/spec';
|
||||
|
||||
defineTestsFromModule('decoder');
|
3
tests/encoder.spec.ts
Normal file
3
tests/encoder.spec.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineTestsFromModule } from './utils/spec';
|
||||
|
||||
defineTestsFromModule('encoder');
|
96
tests/types/webassembly/index.d.ts
vendored
Normal file
96
tests/types/webassembly/index.d.ts
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* WebAssembly v1 (MVP) declaration file for TypeScript
|
||||
* Definitions by: 01alchemist (https://twitter.com/01alchemist)
|
||||
*/
|
||||
declare namespace WebAssembly {
|
||||
/**
|
||||
* WebAssembly.Module
|
||||
**/
|
||||
class Module {
|
||||
constructor(bufferSource: ArrayBuffer | ArrayBufferView<number>);
|
||||
|
||||
static customSections(module: Module, sectionName: string): ArrayBuffer[];
|
||||
static exports(module: Module): { name: string, kind: string }[];
|
||||
static imports(module: Module): { module: string, name: string, kind: string }[];
|
||||
}
|
||||
|
||||
/**
|
||||
* WebAssembly.Instance
|
||||
**/
|
||||
class Instance {
|
||||
readonly exports: any;
|
||||
constructor(module: Module, importObject?: any);
|
||||
}
|
||||
|
||||
/**
|
||||
* WebAssembly.Memory
|
||||
* Note: A WebAssembly page has a constant size of 65,536 bytes, i.e., 64KiB.
|
||||
**/
|
||||
interface MemoryDescriptor {
|
||||
initial: number;
|
||||
maximum?: number;
|
||||
}
|
||||
|
||||
class Memory {
|
||||
readonly buffer: ArrayBuffer;
|
||||
constructor(memoryDescriptor: MemoryDescriptor);
|
||||
grow(numPages: number): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebAssembly.Table
|
||||
**/
|
||||
interface TableDescriptor {
|
||||
element: "anyfunc",
|
||||
initial: number;
|
||||
maximum?: number;
|
||||
}
|
||||
|
||||
class Table {
|
||||
readonly length: number;
|
||||
constructor(tableDescriptor: TableDescriptor);
|
||||
get(index: number): Function;
|
||||
grow(numElements: number): number;
|
||||
set(index: number, value: Function): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors
|
||||
*/
|
||||
class CompileError extends Error {
|
||||
readonly fileName: string;
|
||||
readonly lineNumber: string;
|
||||
readonly columnNumber: string;
|
||||
constructor(message?: string, fileName?: string, lineNumber?: number);
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
class LinkError extends Error {
|
||||
readonly fileName: string;
|
||||
readonly lineNumber: string;
|
||||
readonly columnNumber: string;
|
||||
constructor(message?: string, fileName?: string, lineNumber?: number);
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
class RuntimeError extends Error {
|
||||
readonly fileName: string;
|
||||
readonly lineNumber: string;
|
||||
readonly columnNumber: string;
|
||||
constructor(message?: string, fileName?: string, lineNumber?: number);
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
function compile(bufferSource: ArrayBuffer | ArrayBufferView<number>): Promise<Module>;
|
||||
|
||||
interface ResultObject {
|
||||
module: Module;
|
||||
instance: Instance;
|
||||
}
|
||||
|
||||
function instantiateStreaming(bufferSource: ArrayBuffer | ArrayBufferView<number>, importObject?: any): Promise<ResultObject>;
|
||||
function instantiate(bufferSource: ArrayBuffer | ArrayBufferView<number>, importObject?: any): Promise<ResultObject>;
|
||||
function instantiate(module: Module, importObject?: any): Promise<Instance>;
|
||||
|
||||
function validate(bufferSource: ArrayBuffer | ArrayBufferView<number>): boolean;
|
||||
}
|
3
tests/types/webassembly/package.json
Normal file
3
tests/types/webassembly/package.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"types": "index.d.ts"
|
||||
}
|
114
tests/utils/helpers.ts
Normal file
114
tests/utils/helpers.ts
Normal file
@ -0,0 +1,114 @@
|
||||
/// <reference path="../types/webassembly/index.d.ts" />
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
|
||||
import { demangle } from 'assemblyscript/lib/loader';
|
||||
|
||||
const DIGITALS_REGEXP = /([0-9]{1,})/g;
|
||||
const UPPER_ALPHAS_REGEXP = /([A-Z]{1,})/g;
|
||||
|
||||
export type ImportEntries = { [key: string]: object };
|
||||
export type ExportedEntry = { [key: string]: Function };
|
||||
export type ExportedEntries = { [key: string]: ExportedEntry };
|
||||
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
|
||||
const F64 = new Float64Array(1);
|
||||
const U64 = new Uint32Array(F64.buffer);
|
||||
|
||||
export function decamelize(str: string): string {
|
||||
const t = str
|
||||
.replace(DIGITALS_REGEXP, ' $1')
|
||||
.replace(UPPER_ALPHAS_REGEXP, m => ' ' + (m.length === 1 ? m.toLowerCase() : m));
|
||||
return t.charAt(0).toUpperCase() + t.slice(1);
|
||||
}
|
||||
|
||||
export async function setup(testFileName: string): Promise<ExportedEntries> {
|
||||
const pathName = path.resolve(__dirname, `../build/${ testFileName }.wasm`);
|
||||
const file = await readFile(pathName, null);
|
||||
if (!WebAssembly.validate(file)) {
|
||||
throw new Error(`WebAssembly binary "${ pathName }" file not valid!`);
|
||||
}
|
||||
const imports = buildImports(`${ testFileName }.spec.as`, new WebAssembly.Memory({ initial: 2 }));
|
||||
const result = await WebAssembly.instantiate(file, imports);
|
||||
return demangle<ExportedEntries>(result.instance.exports);
|
||||
}
|
||||
|
||||
function unpackToString64(value: number): string {
|
||||
F64[0] = value;
|
||||
return U64[1].toString(16) + U64[0].toString(16);
|
||||
}
|
||||
|
||||
function unpackToString128(lo: number, hi: number): string {
|
||||
return `0x${ (unpackToString64(hi) + unpackToString64(lo)).padStart(32, '0') }`;
|
||||
}
|
||||
|
||||
function getString(ptr: number, buffer: ArrayBuffer): string {
|
||||
var U16 = new Uint16Array(buffer);
|
||||
var U32 = new Uint32Array(buffer);
|
||||
var dataLength = U32[ptr >>> 2];
|
||||
var dataOffset = (ptr + 4) >>> 1;
|
||||
var dataRemain = dataLength;
|
||||
var parts = [];
|
||||
const chunkSize = 1024;
|
||||
while (dataRemain > chunkSize) {
|
||||
let last = U16[dataOffset + chunkSize - 1];
|
||||
let size = last >= 0xD800 && last < 0xDC00 ? chunkSize - 1 : chunkSize;
|
||||
let part = U16.subarray(dataOffset, dataOffset += size);
|
||||
parts.push(String.fromCharCode.apply(String, part));
|
||||
dataRemain -= size;
|
||||
}
|
||||
return parts.join('') + String.fromCharCode.apply(String, U16.subarray(dataOffset, dataOffset + dataRemain));
|
||||
}
|
||||
|
||||
function buildImports(name: string, memory: WebAssembly.Memory): ImportEntries {
|
||||
const buffer = memory.buffer;
|
||||
return {
|
||||
env: {
|
||||
memory,
|
||||
abort(msgPtr: number, filePtr: number, line: number, column: number) {
|
||||
if (msgPtr) {
|
||||
throw new Error(
|
||||
`Abort called by reason "${ getString(msgPtr, buffer) }" at ${ getString(filePtr, buffer) } [${ line }:${ column }]`
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Abort called at ${ getString(filePtr, buffer) } [${ line }:${ column }]`);
|
||||
}
|
||||
},
|
||||
},
|
||||
// TODO: Don't hardcode support for encoder/decoder
|
||||
decoder: {
|
||||
logStr(msgPtr: number) {
|
||||
if (msgPtr) console.log(`[str]: ${ getString(msgPtr, buffer) }`);
|
||||
},
|
||||
logF64(value: number) {
|
||||
console.log(`[f64]: ${ value }`);
|
||||
},
|
||||
},
|
||||
encoder: {
|
||||
logStr(msgPtr: number) {
|
||||
if (msgPtr) console.log(`[str]: ${ getString(msgPtr, buffer) }`);
|
||||
},
|
||||
logF64(value: number) {
|
||||
console.log(`[f64]: ${ value }`);
|
||||
},
|
||||
},
|
||||
[name]: {
|
||||
logF64(value: number) {
|
||||
console.log(`[f64]: ${ value }`);
|
||||
},
|
||||
logStr(msgPtr: number) {
|
||||
if (msgPtr) console.log(`[str]: ${ getString(msgPtr, buffer) }`);
|
||||
},
|
||||
logU128Packed(msgPtr: number, lo: number, hi: number) {
|
||||
if (msgPtr) {
|
||||
console.log(`[u128] ${ getString(msgPtr, buffer) }: ${ unpackToString128(lo, hi) }`);
|
||||
} else {
|
||||
console.log(`[u128]: ${ unpackToString128(lo, hi) }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
34
tests/utils/spec.ts
Normal file
34
tests/utils/spec.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import test from 'ava';
|
||||
import { setup, decamelize } from './helpers';
|
||||
|
||||
export async function defineTestsFromModule(moduleName: string) {
|
||||
try {
|
||||
const instance = await setup(moduleName);
|
||||
|
||||
// TODO: Refactor into proper testing framework for AssemblyScript
|
||||
for (const tests in instance) {
|
||||
const testsInstance = instance[tests];
|
||||
|
||||
if (testsInstance.setUp) {
|
||||
test.beforeEach(() => {
|
||||
testsInstance.setUp();
|
||||
});
|
||||
}
|
||||
if (testsInstance.tearDown) {
|
||||
test.afterEach(() => {
|
||||
testsInstance.tearDown();
|
||||
});
|
||||
}
|
||||
for (const testName of Object.keys(testsInstance).filter(it => !(["setUp", "tearDown"].indexOf(it) != -1))) {
|
||||
if (testName.startsWith("shouldAbort")) {
|
||||
test(decamelize(testName), t => { t.throws(() => testsInstance[testName]()) });
|
||||
} else {
|
||||
test(decamelize(testName), t => t.truthy(testsInstance[testName]()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Error loading WebAssembly module:", e);
|
||||
throw e;
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user