import { MAX_BLENGTH, HEADER_SIZE, allocateUnsafe, reallocateUnsafe, loadUnsafe, storeUnsafe } from "./internal/arraybuffer"; import { allocateUnsafe as allocateUnsafeString, freeUnsafe as freeUnsafeString, copyUnsafe as copyUnsafeString } from "./internal/string"; import { defaultComparator, insertionSort, weakHeapSort } from "./internal/array"; import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./internal/number"; 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"); var byteLength = length << alignof(); var buffer = allocateUnsafe(byteLength); this.buffer_ = buffer; this.length_ = length; memory.fill( changetype(buffer) + HEADER_SIZE, 0, byteLength ); } @inline 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 = reallocateUnsafe(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()) ? loadUnsafe(buffer, index) : unreachable(); } @operator("{}") private __unchecked_get(index: i32): T { return loadUnsafe(this.buffer_, index); } @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 = reallocateUnsafe(buffer, (index + 1) << alignof()); this.buffer_ = buffer; this.length_ = index + 1; } storeUnsafe(buffer, index, value); if (isManaged()) __gc_link(changetype(this), changetype(value)); // tslint:disable-line } @operator("{}=") private __unchecked_set(index: i32, value: T): void { storeUnsafe(this.buffer_, index, value); if (isManaged()) __gc_link(changetype(this), changetype(value)); // tslint:disable-line } fill(value: T, start: i32 = 0, end: i32 = i32.MAX_VALUE): this { var buffer = this.buffer_; var len = this.length_; start = start < 0 ? max(len + start, 0) : min(start, len); end = end < 0 ? max(len + end, 0) : min(end, len); if (sizeof() == 1) { if (start < end) { memory.fill( changetype(buffer) + start + HEADER_SIZE, value, (end - start) ); } } else { for (; start < end; ++start) { storeUnsafe(buffer, start, value); } } return this; } @inline includes(searchElement: T, fromIndex: i32 = 0): bool { return this.indexOf(searchElement, fromIndex) >= 0; } 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 = reallocateUnsafe(buffer, newLength << alignof()); this.buffer_ = buffer; } this.length_ = newLength; storeUnsafe(buffer, length, element); if (isManaged()) __gc_link(changetype(this), changetype(element)); // tslint:disable-line return newLength; } concat(items: Array): Array { var thisLen: isize = this.length_; var otherLen = (items == null) ? 0 : items.length_; var outLen = thisLen + otherLen; var out: Array = new Array(outLen); if (thisLen) { memory.copy(changetype(out.buffer_) + HEADER_SIZE, changetype(this.buffer_) + HEADER_SIZE, (thisLen << alignof())); } if (otherLen) { memory.copy(changetype(out.buffer_) + HEADER_SIZE + (thisLen << alignof()), changetype(items.buffer_) + HEADER_SIZE, (otherLen << alignof())); } return out; } 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; memory.copy( changetype(buffer) + HEADER_SIZE, changetype(buffer) + HEADER_SIZE + sizeof(), lastIndex << alignof() ); storeUnsafe(buffer, lastIndex, null); 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 = reallocateUnsafe(buffer, newLength << alignof()); capacity = buffer.byteLength >>> alignof(); this.buffer_ = buffer; } memory.copy( changetype(buffer) + HEADER_SIZE + sizeof(), changetype(buffer) + HEADER_SIZE, (capacity - 1) << alignof() ); storeUnsafe(buffer, 0, element); this.length_ = newLength; if (isManaged()) __gc_link(changetype(this), changetype(element)); // tslint:disable-line 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) { memory.copy( changetype(sliced.buffer_) + HEADER_SIZE, changetype(this.buffer_) + HEADER_SIZE + (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_; memory.copy( changetype(buffer) + HEADER_SIZE + (start << alignof()), changetype(buffer) + HEADER_SIZE + ((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 { // TODO remove this when flow will allow trackcing null assert(comparator); // The comparison function must be a function 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; } if (isReference()) { // TODO replace this to faster stable sort (TimSort) when it implemented insertionSort(buffer, 0, length, comparator); return this; } else { if (length < 256) { insertionSort(buffer, 0, length, comparator); } else { weakHeapSort(buffer, 0, length, comparator); } return this; } } join(separator: string = ","): string { var lastIndex = this.length_ - 1; if (lastIndex < 0) return ""; var result = ""; var value: T; var buffer = this.buffer_; var sepLen = separator.length; var hasSeparator = sepLen != 0; if (value instanceof bool) { if (!lastIndex) { return select("true", "false", loadUnsafe(buffer, 0)); } let valueLen = 5; // max possible length of element len("false") let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = allocateUnsafeString(estLen); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = loadUnsafe(buffer, i); valueLen = 4 + (!value); copyUnsafeString(result, offset, select("true", "false", value), 0, valueLen); offset += valueLen; if (hasSeparator) { copyUnsafeString(result, offset, changetype(separator), 0, sepLen); offset += sepLen; } } value = loadUnsafe(buffer, lastIndex); valueLen = 4 + (!value); copyUnsafeString(result, offset, select("true", "false", value), 0, valueLen); offset += valueLen; let out = result; if (estLen > offset) { out = result.substring(0, offset); freeUnsafeString(result); } return out; } else if (isInteger()) { if (!lastIndex) { return changetype(itoa(loadUnsafe(buffer, 0))); } const valueLen = (sizeof() <= 4 ? 10 : 20) + isSigned(); let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = allocateUnsafeString(estLen); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = loadUnsafe(buffer, i); offset += itoa_stream(changetype(result), offset, value); if (hasSeparator) { copyUnsafeString(result, offset, separator, 0, sepLen); offset += sepLen; } } value = loadUnsafe(buffer, lastIndex); offset += itoa_stream(changetype(result), offset, value); let out = result; if (estLen > offset) { out = result.substring(0, offset); freeUnsafeString(result); } return out; } else if (isFloat()) { if (!lastIndex) { return changetype(dtoa(loadUnsafe(buffer, 0))); } const valueLen = MAX_DOUBLE_LENGTH; let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = allocateUnsafeString(estLen); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = loadUnsafe(buffer, i); offset += dtoa_stream(changetype(result), offset, value); if (hasSeparator) { copyUnsafeString(result, offset, separator, 0, sepLen); offset += sepLen; } } value = loadUnsafe(buffer, lastIndex); offset += dtoa_stream(changetype(result), offset, value); let out = result; if (estLen > offset) { out = result.substring(0, offset); freeUnsafeString(result); } return out; } else if (isString()) { if (!lastIndex) { return loadUnsafe(buffer, 0); } let estLen = 0; for (let i = 0, len = lastIndex + 1; i < len; ++i) { estLen += loadUnsafe(buffer, i).length; } let offset = 0; let result = allocateUnsafeString(estLen + sepLen * lastIndex); for (let i = 0; i < lastIndex; ++i) { value = loadUnsafe(buffer, i); if (value) { let valueLen = value.length; // tslint:disable-line:no-unsafe-any copyUnsafeString(result, offset, value, 0, valueLen); // tslint:disable-line:no-unsafe-any offset += valueLen; // tslint:disable-line:no-unsafe-any } if (hasSeparator) { copyUnsafeString(result, offset, separator, 0, sepLen); offset += sepLen; } } value = loadUnsafe(buffer, lastIndex); if (value) { let valueLen = value.length; // tslint:disable-line:no-unsafe-any copyUnsafeString(result, offset, value, 0, valueLen); // tslint:disable-line:no-unsafe-any } return result; } else if (isArray()) { if (!lastIndex) { value = loadUnsafe(buffer, 0); return value ? value.join(separator) : ""; // tslint:disable-line:no-unsafe-any } for (let i = 0; i < lastIndex; ++i) { value = loadUnsafe(buffer, i); if (value) result += value.join(separator); // tslint:disable-line:no-unsafe-any if (hasSeparator) result += separator; } value = loadUnsafe(buffer, lastIndex); if (value) result += value.join(separator); // tslint:disable-line:no-unsafe-any return result; } else if (isReference()) { // References if (!lastIndex) return "[object Object]"; const valueLen = 15; // max possible length of element len("[object Object]") let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = allocateUnsafeString(estLen); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = loadUnsafe(buffer, i); if (value) { copyUnsafeString(result, offset, changetype("[object Object]"), 0, valueLen); offset += valueLen; } if (hasSeparator) { copyUnsafeString(result, offset, changetype(separator), 0, sepLen); offset += sepLen; } } if (loadUnsafe(buffer, lastIndex)) { copyUnsafeString(result, offset, changetype("[object Object]"), 0, valueLen); offset += valueLen; } let out = result; if (estLen > offset) { out = result.substring(0, offset); freeUnsafeString(result); } return out; } else { assert(false); // Unsupported generic typename } } @inline toString(): string { return this.join(); } private __gc(): void { var buffer = this.buffer_; __gc_mark(changetype(buffer)); // tslint:disable-line if (isManaged()) { let offset: usize = 0; let end = this.length_ << alignof(); while (offset < end) { __gc_mark(load(changetype(buffer) + offset, HEADER_SIZE)); // tslint:disable-line offset += sizeof(); } } } }