import { ALLOC, REALLOC, REGISTER, LINK, ArrayBufferView, FREE } from "./runtime"; import { ArrayBuffer } from "./arraybuffer"; import { COMPARATOR, SORT } from "./util/sort"; import { itoa, dtoa, itoa_stream, dtoa_stream, MAX_DOUBLE_LENGTH } from "./util/number"; import { isArray as builtin_isArray } from "./builtins"; export class Array extends ArrayBufferView { private length_: i32; @inline static isArray(value: U): bool { return builtin_isArray(value) && value !== null; } constructor(length: i32 = 0) { super(length, alignof()); this.length_ = length; } get length(): i32 { return this.length_; } set length(length: i32) { this.resize(length); this.length_ = length; } resize(length: i32): void { var buffer = this.buffer; var oldCapacity = buffer.byteLength >>> alignof(); if (length > oldCapacity) { const MAX_LENGTH = ArrayBuffer.MAX_BYTELENGTH >>> alignof(); if (length > MAX_LENGTH) throw new RangeError("Invalid array length"); let newCapacity = length << alignof(); let newBuffer = REALLOC(changetype(buffer), newCapacity); if (newBuffer !== changetype(buffer)) { this.buffer = changetype(newBuffer); // links this.dataStart = newBuffer; this.dataEnd = newBuffer + newCapacity; } } } every(callbackfn: (element: T, index: i32, array: Array) => bool): bool { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { if (!callbackfn(load(this.dataStart + (index << alignof())), index, this)) return false; } return true; } findIndex(predicate: (element: T, index: i32, array: Array) => bool): i32 { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { if (predicate(load(this.dataStart + (index << alignof())), index, this)) return index; } return -1; } // @operator("[]") // private __get(index: i32): T { // var buffer = this.buffer_; // return index < (buffer.byteLength >>> alignof()) // ? LOAD(buffer, index) // : unreachable(); // } // @operator("{}") // private __unchecked_get(index: i32): T { // return LOAD(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; // } // STORE(buffer, index, value); // if (isManaged()) __gc_link(changetype(this), changetype(value)); // tslint:disable-line // } // @operator("{}=") // private __unchecked_set(index: i32, value: T): void { // STORE(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 base = this.dataStart; var length = this.length_; start = start < 0 ? max(length + start, 0) : min(start, length); end = end < 0 ? max(length + end, 0) : min(end, length); if (sizeof() == 1) { if (start < end) { memory.fill( base + start, value, (end - start) ); } } else { for (; start < end; ++start) { store(base + (start << alignof()), value); } } return this; } 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 base = this.dataStart; while (fromIndex < length) { if (load(base + (fromIndex << alignof())) == 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 base = this.dataStart; while (fromIndex >= 0) { // ^ if (load(base + (fromIndex << alignof())) == searchElement) return fromIndex; --fromIndex; } return -1; } push(element: T): i32 { var newLength = this.length_ + 1; this.resize(newLength); this.length_ = newLength; store(this.dataStart + ((newLength - 1) << alignof()), element); if (isManaged()) LINK(changetype(element), changetype(this)); return newLength; } concat(other: Array): Array { var thisLen = this.length_; var otherLen = select(0, other.length_, other === null); var outLen = thisLen + otherLen; var out = new Array(outLen); if (thisLen) { memory.copy(out.dataStart, this.dataStart, thisLen << alignof()); } if (otherLen) { memory.copy(out.dataStart + (thisLen << alignof()), other.dataStart, otherLen << alignof()); } return out; } copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): this { var buffer = this.buffer_; var len = this.length_; end = min(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(buffer, to, LOAD(buffer, from)); --from, --to, --count; } } else { memory.copy( changetype(buffer) + HEADER_SIZE + (to << alignof()), changetype(buffer) + HEADER_SIZE + (from << alignof()), count << alignof() ); } return this; } pop(): T { var length = this.length_; if (length < 1) throw new RangeError("Array is empty"); var element = load(this.dataStart + (--length << alignof())); this.length_ = length; return element; } forEach(callbackfn: (value: T, index: i32, array: Array) => void): void { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { callbackfn(load(this.dataStart + (index << alignof())), index, this); } } map(callbackfn: (value: T, index: i32, array: Array) => U): Array { var length = this.length_; var result = new Array(length); var resultStart = result.dataStart; for (let index = 0; index < min(length, this.length_); ++index) { let element = load(this.dataStart + (index << alignof())); store(resultStart + (index << alignof()), element); if (isManaged()) LINK(changetype(element), changetype(result)); } return result; } filter(callbackfn: (value: T, index: i32, array: Array) => bool): Array { var result = new Array(); for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { let value = load(this.dataStart + (index << alignof())); 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; for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { accum = callbackfn(accum, load(this.dataStart + (index << alignof())), index, this); } return accum; } reduceRight( callbackfn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array) => U, initialValue: U ): U { var accum = initialValue; for (let index = this.length_ - 1; index >= 0; --index) { accum = callbackfn(accum, load(this.dataStart + (index << alignof())), index, this); } return accum; } shift(): T { var length = this.length_; if (length < 1) throw new RangeError("Array is empty"); var base = this.dataStart; var element = load(base); var lastIndex = length - 1; memory.copy( base, base + sizeof(), lastIndex << alignof() ); store(base + (lastIndex << alignof()), null); this.length_ = lastIndex; return element; } some(callbackfn: (element: T, index: i32, array: Array) => bool): bool { for (let index = 0, length = this.length_; index < min(length, this.length_); ++index) { if (callbackfn(load(this.dataStart + (index << alignof())), index, this)) return true; } return false; } unshift(element: T): i32 { var newLength = this.length_; this.resize(newLength); var base = this.dataStart; memory.copy( base + sizeof(), base, (newLength - 1) << alignof() ); store(base, element); if (isManaged()) LINK(changetype(element), changetype(this)); this.length_ = newLength; return newLength; } slice(begin: i32 = 0, end: i32 = i32.MAX_VALUE): Array { var length = this.length_; begin = begin < 0 ? max(begin + length, 0) : min(begin, length); end = end < 0 ? max(end + length, 0) : min(end , length); length = max(end - begin, 0); var slice = new Array(length); var sliceBase = slice.dataStart; var thisBase = this.dataStart + (begin << alignof()); for (let i = 0; i < length; ++i) { let offset = i << alignof(); let element = load(thisBase + offset); store(sliceBase + offset, element); if (isManaged()) LINK(changetype(element), changetype(slice)); } return slice; } splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array { var length = this.length_; start = start < 0 ? max(length + start, 0) : min(start, length); deleteCount = max(min(deleteCount, length - start), 0); var splice = new Array(deleteCount); var spliceStart = splice.dataStart; var thisStart = this.dataStart; var thisBase = thisStart + (start << alignof()); for (let i = 0; i < deleteCount; ++i) { let element = load(thisBase + (i << alignof())); store(spliceStart + (i << alignof()), element); if (isManaged()) LINK(changetype(element), changetype(splice)); } memory.copy( splice.dataStart, thisBase, deleteCount << alignof() ); var offset = start + deleteCount; if (length != offset) { memory.copy( thisBase, thisStart + (offset << alignof()), (length - offset) << alignof() ); } this.length_ = length - deleteCount; return splice; } reverse(): Array { var base = this.dataStart; for (let front = 0, back = this.length_ - 1; front < back; ++front, --back) { let temp: T = load(base, front); store(base + (front << alignof()), load(base + (back << alignof()))); store(base + (back << alignof()), temp); } return this; } sort(comparator: (a: T, b: T) => i32 = COMPARATOR()): this { // TODO remove this when flow will allow tracking null assert(comparator); // The comparison function must be a function var length = this.length_; if (length <= 1) return this; var base = this.dataStart; if (length == 2) { let a: T = load(base, sizeof()); // a = arr[1] let b: T = load(base); // b = arr[0] if (comparator(a, b) < 0) { store(base, b, sizeof()); // arr[1] = b; store(base, a); // arr[0] = a; } return this; } SORT(base, length, comparator); return this; } // FIXME: refactor into multiple functions? join(separator: string = ","): string { var lastIndex = this.length_ - 1; if (lastIndex < 0) return ""; var result = ""; var value: T; var base = this.dataStart; // var buffer = this.buffer_; var sepLen = separator.length; var hasSeparator = sepLen != 0; if (value instanceof bool) { if (!lastIndex) return select("true", "false", load(base)); let valueLen = 5; // max possible length of element len("false") let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = ALLOC(estLen << 1); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = load(base + i); valueLen = 4 + (!value); memory.copy( result + (offset << 1), changetype(select("true", "false", value)), valueLen << 1 ); offset += valueLen; if (hasSeparator) { memory.copy( result + (offset << 1), changetype(separator), sepLen << 1 ); offset += sepLen; } } value = load(base + lastIndex); valueLen = 4 + (!value); memory.copy( result + (offset << 1), changetype(select("true", "false", value)), valueLen << 1 ); offset += valueLen; if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); FREE(result); return trimmed; // registered in .substring } return REGISTER(result); } else if (isInteger()) { if (!lastIndex) return changetype(itoa(load(base))); const valueLen = (sizeof() <= 4 ? 10 : 20) + isSigned(); let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = ALLOC(estLen << 1); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = load(base + (i << alignof())); offset += itoa_stream(result, offset, value); if (hasSeparator) { memory.copy( result + (offset << 1), changetype(separator), sepLen << 1 ); offset += sepLen; } } value = load(base + (lastIndex << alignof())); offset += itoa_stream(result, offset, value); if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); FREE(result); return trimmed; // registered in .substring } return REGISTER(result); } else if (isFloat()) { if (!lastIndex) return changetype(dtoa(load(base))); const valueLen = MAX_DOUBLE_LENGTH; let estLen = (valueLen + sepLen) * lastIndex + valueLen; let result = ALLOC(estLen << 1); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = load(base + (i << alignof())); offset += dtoa_stream(result, offset, value); if (hasSeparator) { memory.copy( result + (offset << 1), changetype(separator), sepLen << 1 ); offset += sepLen; } } value = load(base + (lastIndex << alignof())); offset += dtoa_stream(result, offset, value); if (estLen > offset) { let trimmed = changetype(result).substring(0, offset); FREE(result); return trimmed; // registered in .substring } return REGISTER(result); } else if (isString()) { if (!lastIndex) return load(base); let estLen = 0; for (let i = 0, len = lastIndex + 1; i < len; ++i) { estLen += load(base + (i << alignof())).length; } let offset = 0; let result = ALLOC((estLen + sepLen * lastIndex) << 1); for (let i = 0; i < lastIndex; ++i) { value = load(base + (i << alignof())); if (value) { let valueLen = changetype(value).length; memory.copy( result + (offset << 1), changetype(value), valueLen << 1 ); offset += valueLen; } if (hasSeparator) { memory.copy( result + (offset << 1), changetype(separator), sepLen << 1 ); offset += sepLen; } } value = load(base + (lastIndex << alignof())); if (value) { let valueLen = changetype(value).length; memory.copy( result + (offset << 1), changetype(value), valueLen << 1 ); } return REGISTER(result); } else if (isArray()) { if (!lastIndex) { value = load(base); return value ? value.join(separator) : ""; } for (let i = 0; i < lastIndex; ++i) { value = load(base + (i << alignof())); if (value) result += value.join(separator); if (hasSeparator) result += separator; } value = load(base + (lastIndex << alignof())); if (value) result += value.join(separator); return result; // registered by concatenation (FIXME: lots of garbage) } 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 = ALLOC(estLen << 1); let offset = 0; for (let i = 0; i < lastIndex; ++i) { value = load(base + (i << alignof())); if (value) { memory.copy( result + (offset << 1), changetype("[object Object]"), valueLen << 1 ); offset += valueLen; } if (hasSeparator) { memory.copy( result + (offset << 1), changetype(separator), sepLen << 1 ); offset += sepLen; } } if (load(base + (lastIndex << alignof()))) { memory.copy( result + (offset << 1), changetype("[object Object]"), valueLen << 1 ); offset += valueLen; } if (estLen > offset) { let out = changetype(result).substring(0, offset); FREE(result); return out; // registered in .substring } return REGISTER(result); } else { ERROR("unspported type"); assert(false); } } @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(); // } // } // } }