import { MAX_BLENGTH, HEADER_SIZE, allocateUnsafe, reallocateUnsafe, LOAD, STORE } from "./internal/arraybuffer"; import { allocateUnsafe as allocateUnsafeString, freeUnsafe as freeUnsafeString, copyUnsafe as copyUnsafeString } from "./internal/string"; import { COMPARATOR, SORT } from "./internal/sort"; import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./internal/number"; import { isArray as builtin_isArray } from "./builtins"; export class Array<T> { [key: number]: T; // compatibility only /* @internal */ buffer_: ArrayBuffer; /* @internal */ length_: i32; @inline static isArray<U>(value: U): bool { return builtin_isArray(value) && value !== null; } constructor(length: i32 = 0) { const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>(); if (<u32>length > <u32>MAX_LENGTH) throw new RangeError("Invalid array length"); var byteLength = length << alignof<T>(); var buffer = allocateUnsafe(byteLength); this.buffer_ = buffer; this.length_ = length; memory.fill( changetype<usize>(buffer) + HEADER_SIZE, 0, <usize>byteLength ); } @inline get length(): i32 { return this.length_; } set length(length: i32) { var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof<T>(); if (<u32>length > <u32>capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>(); if (<u32>length > <u32>MAX_LENGTH) throw new RangeError("Invalid array length"); buffer = reallocateUnsafe(buffer, length << alignof<T>()); this.buffer_ = buffer; } this.length_ = length; } every(callbackfn: (element: T, index: i32, array: Array<T>) => bool): bool { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { if (!callbackfn(LOAD<T>(this.buffer_, index), index, this)) return false; } return true; } findIndex(predicate: (element: T, index: i32, array: Array<T>) => bool): i32 { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { if (predicate(LOAD<T>(this.buffer_, index), index, this)) return index; } return -1; } @operator("[]") private __get(index: i32): T { var buffer = this.buffer_; return <u32>index < <u32>(buffer.byteLength >>> alignof<T>()) ? LOAD<T>(buffer, index) : <T>unreachable(); } @operator("{}") private __unchecked_get(index: i32): T { return LOAD<T>(this.buffer_, index); } @operator("[]=") private __set(index: i32, value: T): void { var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof<T>(); if (<u32>index >= <u32>capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>(); if (<u32>index >= <u32>MAX_LENGTH) throw new Error("Invalid array length"); buffer = reallocateUnsafe(buffer, (index + 1) << alignof<T>()); this.buffer_ = buffer; this.length_ = index + 1; } STORE<T>(buffer, index, value); if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(value)); // tslint:disable-line } @operator("{}=") private __unchecked_set(index: i32, value: T): void { STORE<T>(this.buffer_, index, value); if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(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<T>() == 1) { if (start < end) { memory.fill( changetype<usize>(buffer) + start + HEADER_SIZE, <u8>value, <usize>(end - start) ); } } else { for (; start < end; ++start) { STORE<T>(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 (LOAD<T>(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 (LOAD<T>(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<T>(); var newLength = length + 1; // safe only if length is checked if (<u32>length >= <u32>capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>(); if (<u32>length >= <u32>MAX_LENGTH) throw new Error("Invalid array length"); buffer = reallocateUnsafe(buffer, newLength << alignof<T>()); this.buffer_ = buffer; } this.length_ = newLength; STORE<T>(buffer, length, element); if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(element)); // tslint:disable-line return newLength; } concat(items: Array<T>): Array<T> { var thisLen = this.length_; var otherLen = select(0, items.length_, items === null); var outLen = thisLen + otherLen; var out = new Array<T>(outLen); if (thisLen) { memory.copy( changetype<usize>(out.buffer_) + HEADER_SIZE, changetype<usize>(this.buffer_) + HEADER_SIZE, <usize>thisLen << alignof<T>() ); } if (otherLen) { memory.copy( changetype<usize>(out.buffer_) + HEADER_SIZE + (<usize>thisLen << alignof<T>()), changetype<usize>(items.buffer_) + HEADER_SIZE, <usize>otherLen << alignof<T>() ); } return out; } copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this { var buffer = this.buffer_; var len = this.length_; end = min<i32>(end, len); var to = target < 0 ? max(len + target, 0) : min(target, len); var from = start < 0 ? max(len + start, 0) : min(start, len); var last = end < 0 ? max(len + end, 0) : min(end, len); var count = min(last - from, len - to); if (from < to && to < (from + count)) { from += count - 1; to += count - 1; while (count) { STORE<T>(buffer, to, LOAD<T>(buffer, from)); --from, --to, --count; } } else { memory.copy( changetype<usize>(buffer) + HEADER_SIZE + (<usize>to << alignof<T>()), changetype<usize>(buffer) + HEADER_SIZE + (<usize>from << alignof<T>()), <usize>count << alignof<T>() ); } return this; } pop(): T { var length = this.length_; if (length < 1) throw new RangeError("Array is empty"); var element = LOAD<T>(this.buffer_, --length); this.length_ = length; return element; } forEach(callbackfn: (value: T, index: i32, array: Array<T>) => void): void { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { callbackfn(LOAD<T>(this.buffer_, index), index, this); } } map<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U): Array<U> { var length = this.length_; var result = new Array<U>(length); var buffer = result.buffer_; for (let index = 0; index < min(length, this.length_); ++index) { STORE<U>(buffer, index, callbackfn(LOAD<T>(this.buffer_, index), index, this)); } return result; } filter(callbackfn: (value: T, index: i32, array: Array<T>) => bool): Array<T> { var result = new Array<T>(); for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { let value = LOAD<T>(this.buffer_, index); if (callbackfn(value, index, this)) result.push(value); } return result; } reduce<U>( callbackfn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array<T>) => U, initialValue: U ): U { var accum = initialValue; for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { accum = callbackfn(accum, LOAD<T>(this.buffer_, index), index, this); } return accum; } reduceRight<U>( callbackfn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array<T>) => U, initialValue: U ): U { var accum = initialValue; for (let index = this.length_ - 1; index >= 0; --index) { accum = callbackfn(accum, LOAD<T>(this.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 = LOAD<T>(buffer, 0); var lastIndex = length - 1; memory.copy( changetype<usize>(buffer) + HEADER_SIZE, changetype<usize>(buffer) + HEADER_SIZE + sizeof<T>(), <usize>lastIndex << alignof<T>() ); STORE<T>(buffer, lastIndex, <T>null); this.length_ = lastIndex; return element; } some(callbackfn: (element: T, index: i32, array: Array<T>) => bool): bool { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { if (callbackfn(LOAD<T>(this.buffer_, index), index, this)) return true; } return false; } unshift(element: T): i32 { var buffer = this.buffer_; var capacity = buffer.byteLength >>> alignof<T>(); var length = this.length_; var newLength = length + 1; // safe only if length is checked if (<u32>length >= <u32>capacity) { const MAX_LENGTH = MAX_BLENGTH >>> alignof<T>(); if (<u32>length >= <u32>MAX_LENGTH) throw new Error("Invalid array length"); buffer = reallocateUnsafe(buffer, newLength << alignof<T>()); capacity = buffer.byteLength >>> alignof<T>(); this.buffer_ = buffer; } memory.copy( changetype<usize>(buffer) + HEADER_SIZE + sizeof<T>(), changetype<usize>(buffer) + HEADER_SIZE, <usize>(capacity - 1) << alignof<T>() ); STORE<T>(buffer, 0, element); this.length_ = newLength; if (isManaged<T>()) __gc_link(changetype<usize>(this), changetype<usize>(element)); // tslint:disable-line return newLength; } slice(begin: i32 = 0, end: i32 = i32.MAX_VALUE): Array<T> { var len = this.length_; begin = begin < 0 ? max(begin + len, 0) : min(begin, len); end = end < 0 ? max(end + len, 0) : min(end, len); len = end - begin; var sliced = new Array<T>(len); if (len) { memory.copy( changetype<usize>(sliced.buffer_) + HEADER_SIZE, changetype<usize>(this.buffer_) + HEADER_SIZE + (<usize>begin << alignof<T>()), <usize>len << alignof<T>() ); } return sliced; } splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array<T> { var length = this.length_; start = start < 0 ? max<i32>(length + start, 0) : min<i32>(start, length); deleteCount = max<i32>(min<i32>(deleteCount, length - start), 0); var buffer = this.buffer_; var spliced = new Array<T>(deleteCount); var source = changetype<usize>(buffer) + HEADER_SIZE + (<usize>start << alignof<T>()); memory.copy( changetype<usize>(spliced.buffer_) + HEADER_SIZE, source, <usize>deleteCount << alignof<T>() ); var offset = start + deleteCount; if (length != offset) { memory.copy( source, changetype<usize>(buffer) + HEADER_SIZE + (<usize>offset << alignof<T>()), <usize>(length - offset) << alignof<T>() ); } this.length_ = length - deleteCount; return spliced; } reverse(): Array<T> { var buffer = this.buffer_; for (let front = 0, back = this.length_ - 1; front < back; ++front, --back) { let temp = LOAD<T>(buffer, front); STORE<T>(buffer, front, LOAD<T>(buffer, back)); STORE<T>(buffer, back, temp); } return this; } sort(comparator: (a: T, b: T) => i32 = COMPARATOR<T>()): 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 = LOAD<T>(buffer, 1); // a = arr[1] let b = LOAD<T>(buffer, 0); // b = arr[0] if (comparator(a, b) < 0) { STORE<T>(buffer, 1, b); // arr[1] = b; STORE<T>(buffer, 0, a); // arr[0] = a; } return this; } SORT<T>(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<string>("true", "false", LOAD<T,bool>(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 = LOAD<T,bool>(buffer, i); valueLen = 4 + <i32>(!value); copyUnsafeString(result, offset, select<string>("true", "false", value), 0, valueLen); offset += valueLen; if (hasSeparator) { copyUnsafeString(result, offset, changetype<String>(separator), 0, sepLen); offset += sepLen; } } value = LOAD<T,bool>(buffer, lastIndex); valueLen = 4 + <i32>(!value); copyUnsafeString(result, offset, select<string>("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<T>()) { if (!lastIndex) return changetype<string>(itoa<T>(LOAD<T>(buffer, 0))); const valueLen = (sizeof<T>() <= 4 ? 10 : 20) + <i32>isSigned<T>(); let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = allocateUnsafeString(estLen); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = LOAD<T>(buffer, i); offset += itoa_stream<T>(changetype<usize>(result), offset, value); if (hasSeparator) { copyUnsafeString(result, offset, separator, 0, sepLen); offset += sepLen; } } value = LOAD<T>(buffer, lastIndex); offset += itoa_stream<T>(changetype<usize>(result), offset, value); let out = result; if (estLen > offset) { out = result.substring(0, offset); freeUnsafeString(result); } return out; } else if (isFloat<T>()) { if (!lastIndex) return changetype<string>(dtoa(LOAD<T,f64>(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 = LOAD<T,f64>(buffer, i); offset += dtoa_stream(changetype<usize>(result), offset, value); if (hasSeparator) { copyUnsafeString(result, offset, separator, 0, sepLen); offset += sepLen; } } value = LOAD<T,f64>(buffer, lastIndex); offset += dtoa_stream(changetype<usize>(result), offset, value); let out = result; if (estLen > offset) { out = result.substring(0, offset); freeUnsafeString(result); } return out; } else if (isString<T>()) { if (!lastIndex) return LOAD<string>(buffer, 0); let estLen = 0; for (let i = 0, len = lastIndex + 1; i < len; ++i) { estLen += LOAD<string>(buffer, i).length; } let offset = 0; let result = allocateUnsafeString(estLen + sepLen * lastIndex); for (let i = 0; i < lastIndex; ++i) { value = LOAD<string>(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 = LOAD<string>(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<T>()) { if (!lastIndex) { value = LOAD<T>(buffer, 0); return value ? value.join(separator) : ""; // tslint:disable-line:no-unsafe-any } for (let i = 0; i < lastIndex; ++i) { value = LOAD<T>(buffer, i); if (value) result += value.join(separator); // tslint:disable-line:no-unsafe-any if (hasSeparator) result += separator; } value = LOAD<T>(buffer, lastIndex); if (value) result += value.join(separator); // tslint:disable-line:no-unsafe-any return result; } else if (isReference<T>()) { // 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 = LOAD<T>(buffer, i); if (value) { copyUnsafeString(result, offset, changetype<String>("[object Object]"), 0, valueLen); offset += valueLen; } if (hasSeparator) { copyUnsafeString(result, offset, changetype<String>(separator), 0, sepLen); offset += sepLen; } } if (LOAD<T>(buffer, lastIndex)) { copyUnsafeString(result, offset, changetype<String>("[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<usize>(buffer)); // tslint:disable-line if (isManaged<T>()) { let offset: usize = 0; let end = <usize>this.length_ << alignof<usize>(); while (offset < end) { __gc_mark(load<usize>(changetype<usize>(buffer) + offset, HEADER_SIZE)); // tslint:disable-line offset += sizeof<usize>(); } } } }