import { MAX_BLENGTH, HEADER_SIZE as HEADER_SIZE_AB, allocUnsafe, reallocUnsafe, loadUnsafe, storeUnsafe } from "./internal/arraybuffer"; import { defaultComparator, insertionSort, weakHeapSort } from "./internal/array"; export class Array { /* @internal */ buffer_: ArrayBuffer; /* @internal */ length_: i32; constructor(length: i32 = 0) { const MAX_LENGTH = MAX_BLENGTH >>> alignof(); if (length > MAX_LENGTH) throw new RangeError("Invalid array length"); this.buffer_ = allocUnsafe(length << alignof()); this.length_ = length; } get length(): i32 { return this.length_; } set length(length: i32) { var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof(); if (length > capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof(); if (length > MAX_LENGTH) throw new RangeError("Invalid array length"); buffer = reallocUnsafe(buffer, length << alignof()); this.buffer_ = buffer; } this.length_ = length; } every(callbackfn: (element: T, index: i32, array: Array) => bool): bool { var buffer = this.buffer_; for (let index = 0, toIndex = this.length_; index < toIndex && index < this.length_; ++index) { if (!callbackfn(loadUnsafe(buffer, index), index, this)) return false; } return true; } findIndex(predicate: (element: T, index: i32, array: Array) => bool): i32 { var buffer = this.buffer_; for (let index = 0, toIndex = this.length_; index < toIndex && index < this.length_; ++index) { if (predicate(loadUnsafe(buffer, index), index, this)) return index; } return -1; } @operator("[]") private __get(index: i32): T { var buffer = this.buffer_; return index < (buffer.byteLength >>> alignof()) ? load(changetype(buffer) + (index << alignof()), HEADER_SIZE_AB) // ^= loadUnsafe(buffer, index) : unreachable(); // FIXME: using a plain if-else here (as below) results in n-body being about 25% slower? // if (index < (buffer.byteLength >>> alignof())) { // return load(changetype(buffer) + (index << alignof()), HEADER_SIZE_AB); // } else { // throw new Error("Index out of bounds"); // } } @operator("[]=") private __set(index: i32, value: T): void { var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof(); if (index >= capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof(); if (index >= MAX_LENGTH) throw new Error("Invalid array length"); buffer = reallocUnsafe(buffer, (index + 1) << alignof()); this.buffer_ = buffer; this.length_ = index + 1; } store(changetype(buffer) + (index << alignof()), value, HEADER_SIZE_AB); // ^= storeUnsafe(buffer, index, value) } includes(searchElement: T, fromIndex: i32 = 0): bool { var length = this.length_; if (length == 0 || fromIndex >= length) return false; if (fromIndex < 0) fromIndex = max(length + fromIndex, 0); var buffer = this.buffer_; while (fromIndex < length) { if (loadUnsafe(buffer, fromIndex) == searchElement) return true; ++fromIndex; } return false; } indexOf(searchElement: T, fromIndex: i32 = 0): i32 { var length = this.length_; if (length == 0 || fromIndex >= length) return -1; if (fromIndex < 0) fromIndex = max(length + fromIndex, 0); var buffer = this.buffer_; while (fromIndex < length) { if (loadUnsafe(buffer, fromIndex) == searchElement) return fromIndex; ++fromIndex; } return -1; } lastIndexOf(searchElement: T, fromIndex: i32 = this.length_): i32 { var length = this.length_; if (length == 0) return -1; if (fromIndex < 0) fromIndex = length + fromIndex; // no need to clamp else if (fromIndex >= length) fromIndex = length - 1; var buffer = this.buffer_; while (fromIndex >= 0) { // ^ if (loadUnsafe(buffer, fromIndex) == searchElement) return fromIndex; --fromIndex; } return -1; } push(element: T): i32 { var length = this.length_; var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof(); var newLength = length + 1; // safe only if length is checked if (length >= capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof(); if (length >= MAX_LENGTH) throw new Error("Invalid array length"); buffer = reallocUnsafe(buffer, newLength << alignof()); this.buffer_ = buffer; } this.length_ = newLength; storeUnsafe(buffer, length, element); return newLength; } pop(): T { var length = this.length_; if (length < 1) throw new RangeError("Array is empty"); var element = loadUnsafe(this.buffer_, --length); this.length_ = length; return element; } forEach(callbackfn: (value: T, index: i32, array: Array) => void): void { var buffer = this.buffer_; for (let index = 0, toIndex = this.length_; index < toIndex && index < this.length_; ++index) { callbackfn(loadUnsafe(buffer, index), index, this); } } map(callbackfn: (value: T, index: i32, array: Array) => U): Array { var buffer = this.buffer_; var length = this.length_; var result = new Array(length); var resultBuffer = result.buffer_; for (let index = 0; index < length && index < this.length_; ++index) { storeUnsafe(resultBuffer, index, callbackfn(loadUnsafe(buffer, index), index, this)); } return result; } filter(callbackfn: (value: T, index: i32, array: Array) => bool): Array { var buffer = this.buffer_; var length = this.length_; var result = new Array(); for (let index = 0; index < length && index < this.length_; ++index) { let value = loadUnsafe(buffer, index); if (callbackfn(value, index, this)) result.push(value); } return result; } reduce( callbackfn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array) => U, initialValue: U ): U { var accum = initialValue; var buffer = this.buffer_; for (let index = 0, toIndex = this.length_; index < toIndex && index < this.length_; ++index) { accum = callbackfn(accum, loadUnsafe(buffer, index), index, this); } return accum; } reduceRight( callbackfn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array) => U, initialValue: U ): U { var accum = initialValue; var buffer = this.buffer_; for (let index: i32 = this.length_ - 1; index >= 0; --index) { accum = callbackfn(accum, loadUnsafe(buffer, index), index, this); } return accum; } shift(): T { var length = this.length_; if (length < 1) throw new RangeError("Array is empty"); var buffer = this.buffer_; var element = loadUnsafe(buffer, 0); var lastIndex = length - 1; move_memory( changetype(buffer) + HEADER_SIZE_AB, changetype(buffer) + HEADER_SIZE_AB + sizeof(), lastIndex << alignof() ); storeUnsafe(buffer, lastIndex, isReference() ? null : 0); this.length_ = lastIndex; return element; } some(callbackfn: (element: T, index: i32, array: Array) => bool): bool { var buffer = this.buffer_; for (let index = 0, toIndex = this.length_; index < toIndex && index < this.length_; ++index) { if (callbackfn(loadUnsafe(buffer, index), index, this)) return true; } return false; } unshift(element: T): i32 { var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof(); var length = this.length_; var newLength = length + 1; // safe only if length is checked if (length >= capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof(); if (length >= MAX_LENGTH) throw new Error("Invalid array length"); buffer = reallocUnsafe(buffer, newLength << alignof()); capacity = buffer.byteLength >>> alignof(); this.buffer_ = buffer; } move_memory( changetype(buffer) + HEADER_SIZE_AB + sizeof(), changetype(buffer) + HEADER_SIZE_AB, (capacity - 1) << alignof() ); storeUnsafe(buffer, 0, element); this.length_ = newLength; return newLength; } slice(begin: i32 = 0, end: i32 = i32.MAX_VALUE): Array { var length = this.length_; if (begin < 0) begin = max(length + begin, 0); else if (begin > length) begin = length; if (end < 0) end = length + end; // no need to clamp else if (end > length) end = length; if (end < begin) end = begin; // ^ var newLength = end - begin; assert(newLength >= 0); var sliced = new Array(newLength); if (newLength) { move_memory( changetype(sliced.buffer_) + HEADER_SIZE_AB, changetype(this.buffer_) + HEADER_SIZE_AB + (begin << alignof()), newLength << alignof() ); } return sliced; } splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): void { if (deleteCount < 1) return; var length = this.length_; if (start < 0) start = max(length + start, 0); if (start >= length) return; deleteCount = min(deleteCount, length - start); var buffer = this.buffer_; move_memory( changetype(buffer) + HEADER_SIZE_AB + (start << alignof()), changetype(buffer) + HEADER_SIZE_AB + ((start + deleteCount) << alignof()), deleteCount << alignof() ); this.length_ = length - deleteCount; } reverse(): Array { var buffer = this.buffer_; for (let front = 0, back = this.length_ - 1; front < back; ++front, --back) { let temp = loadUnsafe(buffer, front); storeUnsafe(buffer, front, loadUnsafe(buffer, back)); storeUnsafe(buffer, back, temp); } return this; } sort(comparator: (a: T, b: T) => i32 = defaultComparator()): this { var length = this.length_; if (length <= 1) return this; var buffer = this.buffer_; if (length == 2) { let a = loadUnsafe(buffer, 1); // a = arr[1] let b = loadUnsafe(buffer, 0); // b = arr[0] if (comparator(a, b) < 0) { storeUnsafe(buffer, 1, b); // arr[1] = b; storeUnsafe(buffer, 0, a); // arr[0] = a; } return this; } return length < 256 ? insertionSort(this, comparator) : weakHeapSort(this, comparator); } }