Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[Implement] Naive Buffer.from
  • Loading branch information
jtenner committed Jul 19, 2019
commit 62b4dc92ccd67675bedabeb23bc93de65436d1dc
54 changes: 54 additions & 0 deletions assembly/buffer/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BLOCK_MAXSIZE } from "rt/common";
import { E_INVALIDLENGTH, E_INDEXOUTOFRANGE } from "util/error";
import { Uint8Array } from "typedarray";
import { ArrayBufferView } from "arraybuffer";

export class Buffer extends Uint8Array {
constructor(size: i32) {
Expand All @@ -22,4 +23,57 @@ export class Buffer extends Uint8Array {
result.dataLength = size;
return result;
}

// @ts-ignore: Buffer returns on all valid branches
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can suppress this error by returning changetype<Buffer>(null) at the bottom here after the ERROR. What is the right way to do this?

public static from<T>(value: T): Buffer {
// @ts-ignore: AssemblyScript treats this statement correctly
if (value instanceof String[]) {
let length = value.length;
log<i32>(length);
let buffer = __alloc(length, idof<ArrayBuffer>());
let sourceStart = value.dataStart;
for (let i = 0; i < length; i++) {
let str = changetype<string>(load<usize>(sourceStart + (usize(i) << alignof<usize>())));
let value = parseFloat(str); // parseFloat is still naive
store<u8>(buffer + usize(i), isFinite<f64>(value) ? u8(value) : u8(0));
}
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
result.data = changetype<ArrayBuffer>(buffer);
result.dataStart = changetype<usize>(value);
result.dataLength = length;
return result;
} else if (value instanceof ArrayBuffer) {
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
result.data = value;
result.dataStart = changetype<usize>(value);
result.dataLength = value.byteLength;
return result;
} else if (value instanceof String) {
// @ts-ignore value not instance of `string` does changetype<string>(value) work here?
let buffer = String.UTF8.encode(value);
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
result.data = buffer;
result.dataStart = changetype<usize>(buffer);
result.dataLength = buffer.byteLength;
return result;
} else if (value instanceof Buffer) {
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
result.data = value.buffer;
result.dataStart = value.dataStart;
result.dataLength = value.dataLength;
return result;
} else if (value instanceof ArrayBufferView) {
let length = value.length;
let buffer = __alloc(length, idof<ArrayBuffer>());
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
// @ts-ignore: value[i] is implied to work
for (let i = 0; i < length; i++) store<u8>(buffer + usize(i), u8(unchecked(value[i])));
result.data = changetype<ArrayBuffer>(buffer);
result.dataStart = buffer;
result.dataLength = u32(length);
return result;

}
ERROR("Cannot call Buffer.from<T>() where T is not a string, Buffer, ArrayBuffer, Array, or Array-like Object.");
}
}
2 changes: 2 additions & 0 deletions assembly/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ declare class Buffer extends Uint8Array {
static alloc(size: i32): Buffer;
/** This method allocates a new Buffer of indicated size. This is unsafe because the data is not zeroed. */
static allocUnsafe(size: i32): Buffer;
/** This method creates a Buffer from the given reference. This method is naive and defaults to utf8 encoding for strings. */
static from<T>(value: T): Buffer;
}
50 changes: 50 additions & 0 deletions tests/buffer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
function bufferFrom<T>(values: valueof<T>[]): T {
let buffer = instantiate<T>(values.length);
// @ts-ignore
for (let i = 0; i < values.length; i++) buffer[i] = values[i];
return buffer;
}

/**
* This is the buffer test suite. For each prototype function, put a single test
* function call here.
Expand Down Expand Up @@ -42,4 +49,47 @@ describe("buffer", () => {
// TODO: expectFn(() => { Buffer.allocUnsafe(-1); }).toThrow();
// TODO: expectFn(() => { Buffer.allocUnsafe(BLOCK_MAXSIZE + 1); }).toThrow();
});


/**
* This specification is a tradeoff, because Buffer.from() takes _many_ parameters.
* Instead, the only common parameter is the first one, which results in Buffer.from
* acting in a very naive fashion. Perhaps an optional encoding parameter might be
* possible for strings, at least. However, this makes things more complicated.
* There are no good solutions. Only tradeoffs. Function overloading is the only
* way to fix this problem.
*/
test("#from", () => {
// TODO: Switch to expect<Buffer>() when 2.2.1 releases

// Buffer.from uses the array buffer reference
let buff = new ArrayBuffer(100);
for (let i = 0; i < 100; i++) store<u8>(changetype<usize>(buff), u8(i));
let abBuffer = Buffer.from<ArrayBuffer>(buff);
expect<ArrayBuffer>(abBuffer.buffer).toStrictEqual(buff);
expect<ArrayBuffer>(abBuffer.buffer).toBe(buff);

// strings are utf8 encoded by default
let strBuffer = Buffer.from<string>("Hello world!");
let strBufferExpected = bufferFrom<Buffer>([0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21]);
expect<ArrayBuffer>(strBuffer.buffer).toStrictEqual(strBufferExpected.buffer);

// buffer returns a new reference view to the same ArrayBuffer
let buff2 = Buffer.from<Buffer>(abBuffer);
expect<Buffer>(buff2).not.toBe(abBuffer);
expect<ArrayBuffer>(buff2.buffer).toBe(abBuffer.buffer);
expect<usize>(buff2.dataStart).toBe(abBuffer.dataStart);
expect<u32>(buff2.dataLength).toBe(abBuffer.dataLength);

// else if it extends ArrayBufferView simply converts all the values
let floats = bufferFrom<Float32Array>([1.1, 2.2, 3.3]);
let floatBuff = Buffer.from<Float32Array>(floats);
let floatBuffExpected = bufferFrom<Buffer>([1, 2, 3]);
expect<ArrayBuffer>(floatBuff.buffer).toStrictEqual(floatBuffExpected.buffer);

let strArrayExpected = bufferFrom<Buffer>([1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
let stringValues: string[] = ["1.1", "2.2", "3.3", "4.4", "5.5", "6.6", "7.7", "Infinity", "NaN", "-Infinity"];
let strArrayActual = Buffer.from<Array<String>>(stringValues);
expect<ArrayBuffer>(strArrayActual.buffer).toStrictEqual(strArrayExpected.buffer);
});
});