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
Prev Previous commit
Next Next commit
[Implement] rest of buffer.from functions per dcode
  • Loading branch information
jtenner committed Aug 22, 2019
commit 45a61c5599ebd5a80743f51501f4e111e389c51c
90 changes: 80 additions & 10 deletions assembly/buffer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { BLOCK_MAXSIZE, BLOCK, BLOCK_OVERHEAD } from "rt/common";
import { E_INVALIDLENGTH, E_INDEXOUTOFRANGE } from "util/error";
import { Uint8Array } from "typedarray";
import { ArrayBufferView } from "arraybuffer";
import { Array } from "array";



export class Buffer extends Uint8Array {
constructor(size: i32) {
Expand All @@ -24,18 +27,85 @@ export class Buffer extends Uint8Array {
return result;
}

public static fromArrayBuffer(buffer: ArrayBuffer, byteOffset: i32 = 0, length: i32 = -1): Buffer {
length = select(buffer.byteLength, length, length < 0);
if (i32(byteOffset < 0) | i32(byteOffset > buffer.byteLength - length)) throw new RangeError(E_INDEXOUTOFRANGE);
if (length == 0) return new Buffer(0);
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
result.data = buffer; // retains
result.dataStart = changetype<usize>(buffer) + <usize>byteOffset;
result.dataLength = <usize>length;
return result;
}

public static fromString(value: string, encoding: string = "utf8"): Buffer {
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>())); // retains
let buffer: ArrayBuffer;
if (encoding == "utf8" || encoding == "utf-8") {
buffer = String.UTF8.encode(value);
} else if (encoding == "utf16le") {
buffer = String.UTF16.encode(value);
} else if (encoding == "hex") {
buffer = Buffer.HEX.encode(value);
// TODO: add ascii encoding
} else {
throw new TypeError("Invalid string encoding.");
}

result.data = buffer; // retains
result.dataStart = changetype<usize>(buffer);
result.dataLength = buffer.byteLength;
return result;
}

public static fromArray<T extends ArrayBufferView>(value: T, offset: i32 = 0, length: i32 = -1): Buffer {
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.

@dcodeIO what is going to happen to this extends clause? Are we going to detach Array from ArrayBufferView?

length = select(value.length, length, length < 0);
if (i32(offset < 0) | i32(offset > value.length)) throw new RangeError(E_INDEXOUTOFRANGE);
if (length > value.length - offset) throw new RangeError(E_INVALIDLENGTH);
if (length == 0) return new Buffer(0);
let abv = __alloc(offsetof<Buffer>(), idof<Buffer>());
let buffer = __alloc(length, idof<ArrayBuffer>());
if (value instanceof Array<String>) {
for (let i = 0; i < length; i++) {
let index = i + offset;
let byteValue = parseFloat(unchecked(value[index]));
store<u8>(buffer + <usize>i, <u8>select(0, <u8>byteValue, isNaN(byteValue)));
}
} else {
for (let i = 0; i < length; i++) {
let index = i + offset;
store<u8>(buffer + <usize>i, <u8>unchecked(value[index]));
}
}
let result = changetype<Buffer>(abv); // retains
result.data = changetype<ArrayBuffer>(buffer); // retains
result.dataStart = buffer;
result.dataLength = length;
return result;
}

public static fromBuffer(source: Buffer): Buffer {
let length = source.dataLength;
let data = changetype<ArrayBuffer>(__alloc(length, idof<ArrayBuffer>())); // retains
memory.copy(changetype<usize>(data), source.dataStart, length);
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>())); // retains
result.data = data;
result.dataStart = changetype<usize>(data);
result.dataLength = length;
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 extends ArrayBufferView>(value: T): Buffer {
public static from<T>(value: T): Buffer {
// @ts-ignore: AssemblyScript treats this statement correctly
if (value instanceof String[]) {
if (value instanceof Array<String>) {
let length = <usize>value.length;
let buffer = __alloc(length, idof<ArrayBuffer>());
let sourceStart = value.dataStart;
for (let i: usize = 0; i < length; i++) {
let str = changetype<string>(load<usize>(sourceStart + (<usize>i << alignof<usize>())));
let value = parseFloat(str); // parseFloat is still naive
trace("float values", 2, value, u8(value));
store<u8>(buffer + i, isFinite(value) ? u8(value) : u8(0));
store<u8>(buffer + i, <u8>select(value, 0.0, isFinite(value)));
}
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
result.data = changetype<ArrayBuffer>(buffer);
Expand All @@ -57,20 +127,20 @@ export class Buffer extends Uint8Array {
result.dataLength = buffer.byteLength;
return result;
} else if (value instanceof Buffer) {
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>())); // retains
result.data = value.buffer;
result.dataStart = value.dataStart;
result.dataLength = value.dataLength;
return result;
} else if (value instanceof ArrayBufferView) {
let length = value.length;
let length = <usize>value.length;
let buffer = __alloc(length, idof<ArrayBuffer>());
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>()));
let result = changetype<Buffer>(__alloc(offsetof<Buffer>(), idof<Buffer>())); // retains
// @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);
for (let i = <usize>0; i < length; i++) store<u8>(buffer + i, <u8>unchecked(value[i]));
result.data = changetype<ArrayBuffer>(buffer); // retains
result.dataStart = buffer;
result.dataLength = u32(length);
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.");
Expand Down
8 changes: 8 additions & 0 deletions assembly/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ declare class Buffer extends Uint8Array {
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;
/** This method creates a buffer from a given string. This method defaults to utf8 encoding. */
public static fromString(value: string, encoding?: string): Buffer;
/** This method creates a buffer that uses the given ArrayBuffer as an underlying value. */
public static fromArrayBuffer(buffer: ArrayBuffer, byteOffset?: i32 , length?: i32): Buffer;
/** This method creates a copy of the buffer using memory.copy(). */
public static fromBuffer(source: Buffer): Buffer;
/** This method creates a new Buffer by copying the underlying values to a new ArrayBuffer and coercing each one to an 8 bit integer value. */
public static fromArray<T extends ArrayBufferView<number | string>>(value: T, offset?: i32, length?: i32): Buffer;
/** This method asserts a value is a Buffer object via `value instanceof Buffer`. */
static isBuffer<T>(value: T): bool;
/** Reads a signed integer at the designated offset. */
Expand Down
70 changes: 67 additions & 3 deletions tests/buffer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function create<T>(values: valueof<T>[]): T {
}

describe("buffer", () => {
test("#constructor", () => {
test(".constructor", () => {
expect<Buffer>(new Buffer(0)).toBeTruthy();
expect<Buffer>(new Buffer(10)).toHaveLength(10);
let myBuffer = new Buffer(10);
Expand All @@ -31,7 +31,7 @@ describe("buffer", () => {
// TODO: expectFn(() => { new Buffer(BLOCK_MAXSIZE + 1); }).toThrow();
});

test("#alloc", () => {
test(".alloc", () => {
expect<Buffer>(Buffer.alloc(10)).toBeTruthy();
expect<Buffer>(Buffer.alloc(10)).toHaveLength(10);
let buff = Buffer.alloc(100);
Expand All @@ -42,7 +42,7 @@ describe("buffer", () => {
// TODO: expectFn(() => { Buffer.alloc(BLOCK_MAXSIZE + 1); }).toThrow();
});

test("#allocUnsafe", () => {
test(".allocUnsafe", () => {
expect<Buffer>(Buffer.allocUnsafe(10)).toBeTruthy();
expect<Buffer>(Buffer.allocUnsafe(10)).toHaveLength(10);
let buff = Buffer.allocUnsafe(100);
Expand Down Expand Up @@ -92,6 +92,70 @@ describe("buffer", () => {
expect<Buffer>(strArrayActual).toStrictEqual(strArrayExpected, "Array Of Strings");
});

test(".fromString", () => {
// public static fromString(value: string, encoding: string = "utf8"): Buffer {
// default encoding is utf8
let expected = create<Buffer>([0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x74, 0xc3, 0xa9, 0x73, 0x74])
expect<Buffer>(Buffer.from('this is a tést'))
.toStrictEqual(expected);

expect<Buffer>(Buffer.fromString('7468697320697320612074c3a97374', 'hex'))
.toStrictEqual(expected);
});

test(".fromArrayBuffer", () => {
const arr = new Uint16Array(2);

arr[0] = 5000;
arr[1] = 4000;

// Shares memory with `arr`.
const buf = Buffer.fromArrayBuffer(arr.buffer);


expect<Buffer>(buf).toStrictEqual(create<Buffer>([0x88, 0x13, 0xa0, 0x0f]));

// Changing the original Uint16Array changes the Buffer also.
arr[1] = 6000;
expect<Buffer>(buf).toStrictEqual(create<Buffer>([0x88, 0x13, 0x70, 0x17]));

// test optional parameters
expect<Buffer>(Buffer.fromArrayBuffer(arr.buffer, 1, 2)).toStrictEqual(create<Buffer>([0x13, 0x70]));

// TODO:
// expectFn(() => {
// let value = create<Uint16Array>([5000, 4000]); // 4 bytes
// Buffer.fromArrayBuffer(value.buffer, 5);
// }).toThrow("offset out of bounds should throw");
// expectFn(() => {
// let value = create<Uint16Array>([5000, 4000]); // 4 bytes
// Buffer.fromArrayBuffer(value.buffer, 2, 3);
// }).toThrow("length out of bounds should throw");
});

test(".fromBuffer", () => {
let buff1 = create<Buffer>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let buff2 = Buffer.fromBuffer(buff1);

expect<Buffer>(buff1).not.toBe(buff2);
expect<ArrayBuffer>(buff1.buffer).not.toBe(buff2.buffer);
expect<Buffer>(buff1).toStrictEqual(buff2);
});

test(".fromArray", () => {
let buff1 = create<Uint16Array>([3, 6, 9, 12, 15, 18, 21]);
let buff2 = Buffer.fromArray(buff1, 2, 4);
let expected = create<Buffer>([9, 12, 15, 18]);
expect<Buffer>(buff2).toStrictEqual(expected);

// test string values
buff2 = Buffer.fromArray<string[]>(["9.2", "12.1", "15.3", "18.8"]);
expect<Buffer>(buff2).toStrictEqual(expected);
});

// todo: fromArray
// todo: fromBuffer

test("#isBuffer", () => {
let a = "";
let b = new Uint8Array(0);
Expand Down