///
import { BLOCK_MAXSIZE } from "./rt/common";
import { Runtime } from "shared/runtime";
import { COMPARATOR, SORT } from "./util/sort";
import { REVERSE, FILL } from "./util/bytes";
import { joinBooleanArray, joinIntegerArray, joinFloatArray, joinStringArray, joinReferenceArray } from "./util/string";
import { idof, isArray as builtin_isArray } from "./builtins";
import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_EMPTYARRAY, E_HOLEYARRAY } from "./util/error";
// @ts-ignore: decorator
@inline @lazy const MIN_SIZE: usize = 8;
/** Ensures that the given array has _at least_ the specified backing size. */
function ensureCapacity(array: usize, newSize: usize, alignLog2: u32, canGrow: bool = true): void {
// Depends on the fact that Arrays mimic ArrayBufferView
let oldCapacity = changetype(array).byteLength;
if (newSize > oldCapacity >>> alignLog2) {
if (newSize > BLOCK_MAXSIZE >>> alignLog2) throw new RangeError(E_INVALIDLENGTH);
let oldData = changetype(changetype(array).buffer);
// Grows old capacity by factor of two.
// Make sure we don't reach BLOCK_MAXSIZE for new growed capacity.
let newCapacity = max(newSize, MIN_SIZE) << alignLog2;
if (canGrow) newCapacity = max(min(oldCapacity << 1, BLOCK_MAXSIZE), newCapacity);
let newData = __renew(oldData, newCapacity);
// __new / __renew already init memory range as zeros in Incremental runtime.
// So try to avoid this.
if (ASC_RUNTIME != Runtime.Incremental) {
memory.fill(newData + oldCapacity, 0, newCapacity - oldCapacity);
}
if (newData != oldData) { // oldData has been free'd
store(array, newData, offsetof("buffer"));
store(array, newData, offsetof("dataStart"));
__link(array, changetype(newData), false);
}
store(array, newCapacity, offsetof("byteLength"));
}
}
export class Array {
[key: number]: T;
// Mimicking ArrayBufferView isn't strictly necessary here but is done to allow glue code
// to work with typed and normal arrays interchangeably. Technically, normal arrays do not need
// `dataStart` (equals `buffer`) and `byteLength` (equals computed `buffer.byteLength`), but the
// block is 16 bytes anyway so it's fine to have a couple extra fields in there.
private buffer: ArrayBuffer;
@unsafe readonly dataStart: usize;
private byteLength: i32; // Uses here as capacity
// Also note that Array with non-nullable T must guard against uninitialized null values
// whenever an element is accessed. Otherwise, the compiler wouldn't be able to guarantee
// type-safety anymore. For lack of a better word, such an array is "holey".
private length_: i32;
static isArray(value: U): bool {
return isReference() ? changetype(value) != 0 && builtin_isArray(value) : false;
}
static create(capacity: i32 = 0): Array {
WARNING("'Array.create' is deprecated. Use 'new Array' instead, making sure initial elements are initialized.");
let array = new Array(capacity);
array.length = 0;
return array;
}
constructor(length: i32 = 0) {
if (length > BLOCK_MAXSIZE >>> alignof()) throw new RangeError(E_INVALIDLENGTH);
// reserve capacity for at least MIN_SIZE elements
let bufferSize = max(length, MIN_SIZE) << alignof();
let buffer = changetype(__new(bufferSize, idof()));
if (ASC_RUNTIME != Runtime.Incremental) {
memory.fill(changetype(buffer), 0, bufferSize);
}
this.buffer = buffer; // links
this.dataStart = changetype(buffer);
this.byteLength = bufferSize;
this.length_ = length;
}
get length(): i32 {
return this.length_;
}
set length(newLength: i32) {
ensureCapacity(changetype(this), newLength, alignof(), false);
this.length_ = newLength;
}
every(fn: (value: T, index: i32, array: Array) => bool): bool {
for (let i = 0, len = this.length_; i < min(len, this.length_); ++i) {
if (!fn(load(this.dataStart + (i << alignof())), i, this)) return false;
}
return true;
}
findIndex(fn: (value: T, index: i32, array: Array) => bool): i32 {
for (let i = 0, len = this.length_; i < min(len, this.length_); ++i) {
if (fn(load(this.dataStart + (i << alignof())), i, this)) return i;
}
return -1;
}
findLastIndex(fn: (value: T, index: i32, array: Array) => bool): i32 {
for (let i = this.length_ - 1; i >= 0; --i) {
if (fn(load(this.dataStart + (i << alignof())), i, this)) return i;
}
return -1;
}
@operator("[]") private __get(index: i32): T {
if (index >= this.length_) throw new RangeError(E_INDEXOUTOFRANGE);
let value = load(this.dataStart + (index << alignof()));
if (isReference()) {
if (!isNullable()) {
if (!changetype(value)) throw new Error(E_HOLEYARRAY);
}
}
return value;
}
@unsafe @operator("{}") private __uget(index: i32): T {
return load(this.dataStart + (index << alignof()));
}
@operator("[]=") private __set(index: i32, value: T): void {
if (index >= this.length_) {
if (index < 0) throw new RangeError(E_INDEXOUTOFRANGE);
ensureCapacity(changetype(this), index + 1, alignof());
this.length_ = index + 1;
}
store(this.dataStart + (index << alignof()), value);
if (isManaged()) {
__link(changetype(this), changetype(value), true);
}
}
at(index: i32): T {
let len = this.length_;
index += select(0, len, index >= 0);
if (index >= len) throw new RangeError(E_INDEXOUTOFRANGE);
let value = load(this.dataStart + (index << alignof()));
if (isReference()) {
if (!isNullable()) {
if (!changetype(value)) throw new Error(E_HOLEYARRAY);
}
}
return value;
}
fill(value: T, start: i32 = 0, end: i32 = i32.MAX_VALUE): Array {
if (isManaged()) {
FILL(this.dataStart, this.length_, changetype(value), start, end);
__link(changetype(this), changetype(value), false);
} else {
FILL(this.dataStart, this.length_, value, start, end);
}
return this;
}
includes(value: T, fromIndex: i32 = 0): bool {
if (isFloat()) {
let len = this.length_;
if (len == 0 || fromIndex >= len) return false;
if (fromIndex < 0) fromIndex = max(len + fromIndex, 0);
let ptr = this.dataStart;
while (fromIndex < len) {
let elem = load(ptr + (fromIndex << alignof()));
// @ts-ignore
if (elem == value || isNaN(elem) & isNaN(value)) return true;
++fromIndex;
}
return false;
} else {
return this.indexOf(value, fromIndex) >= 0;
}
}
indexOf(value: T, fromIndex: i32 = 0): i32 {
let len = this.length_;
if (len == 0 || fromIndex >= len) return -1;
if (fromIndex < 0) fromIndex = max(len + fromIndex, 0);
let ptr = this.dataStart;
while (fromIndex < len) {
if (load(ptr + (fromIndex << alignof())) == value) return fromIndex;
++fromIndex;
}
return -1;
}
lastIndexOf(value: T, fromIndex: i32 = this.length_): i32 {
let len = this.length_;
if (len == 0) return -1;
if (fromIndex < 0) fromIndex = len + fromIndex;
else if (fromIndex >= len) fromIndex = len - 1;
let ptr = this.dataStart;
while (fromIndex >= 0) {
if (load(ptr + (fromIndex << alignof())) == value) return fromIndex;
--fromIndex;
}
return -1;
}
push(value: T): i32 {
let oldLen = this.length_;
let len = oldLen + 1;
ensureCapacity(changetype(this), len, alignof());
if (isManaged()) {
store(this.dataStart + (oldLen << alignof()), changetype(value));
__link(changetype(this), changetype(value), true);
} else {
store(this.dataStart + (oldLen << alignof()), value);
}
this.length_ = len;
return len;
}
concat(other: Array): Array {
let thisLen = this.length_;
let otherLen = other.length_;
let outLen = thisLen + otherLen;
if (outLen > BLOCK_MAXSIZE >>> alignof()) throw new Error(E_INVALIDLENGTH);
let out = changetype>(__newArray(outLen, alignof(), idof>()));
let outStart = out.dataStart;
let thisSize = thisLen << alignof();
if (isManaged()) {
let thisStart = this.dataStart;
for (let offset: usize = 0; offset < thisSize; offset += sizeof()) {
let ref = load(thisStart + offset);
store(outStart + offset, ref);
__link(changetype(out), ref, true);
}
outStart += thisSize;
let otherStart = other.dataStart;
let otherSize = otherLen << alignof();
for (let offset: usize = 0; offset < otherSize; offset += sizeof()) {
let ref = load(otherStart + offset);
store(outStart + offset, ref);
__link(changetype(out), ref, true);
}
} else {
memory.copy(outStart, this.dataStart, thisSize);
memory.copy(outStart + thisSize, other.dataStart, otherLen << alignof());
}
return out;
}
copyWithin(target: i32, start: i32, end: i32 = i32.MAX_VALUE): Array {
let ptr = this.dataStart;
let len = this.length_;
end = min(end, len);
let to = target < 0 ? max(len + target, 0) : min(target, len);
let from = start < 0 ? max(len + start, 0) : min(start, len);
let last = end < 0 ? max(len + end, 0) : min(end, len);
let count = min(last - from, len - to);
memory.copy( // is memmove
ptr + (to << alignof()),
ptr + (from << alignof()),
count << alignof()
);
return this;
}
pop(): T {
let len = this.length_;
if (len < 1) throw new RangeError(E_EMPTYARRAY);
let val = load(this.dataStart + ((--len) << alignof()));
this.length_ = len;
return val;
}
forEach(fn: (value: T, index: i32, array: Array) => void): void {
for (let i = 0, len = this.length_; i < min(len, this.length_); ++i) {
fn(load(this.dataStart + (i << alignof())), i, this);
}
}
map(fn: (value: T, index: i32, array: Array) => U): Array {
let len = this.length_;
let out = changetype>(__newArray(len, alignof(), idof>()));
let outStart = out.dataStart;
for (let i = 0; i < min(len, this.length_); ++i) {
let result = fn(load(this.dataStart + (i << alignof())), i, this);
store(outStart + (i << alignof()), result);
if (isManaged()) {
__link(changetype(out), changetype(result), true);
}
}
return out;
}
filter(fn: (value: T, index: i32, array: Array) => bool): Array {
let result = changetype>(__newArray(0, alignof(), idof>()));
for (let i = 0, len = this.length_; i < min(len, this.length_); ++i) {
let value = load(this.dataStart + (i << alignof()));
if (fn(value, i, this)) result.push(value);
}
return result;
}
reduce(
fn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array) => U,
initialValue: U
): U {
let acc = initialValue;
for (let i = 0, len = this.length_; i < min(len, this.length_); ++i) {
acc = fn(acc, load(this.dataStart + (i << alignof())), i, this);
}
return acc;
}
reduceRight(
fn: (previousValue: U, currentValue: T, currentIndex: i32, array: Array) => U,
initialValue: U
): U {
let acc = initialValue;
for (let i = this.length_ - 1; i >= 0; --i) {
acc = fn(acc, load(this.dataStart + (i << alignof())), i, this);
}
return acc;
}
shift(): T {
let len = this.length_;
if (len < 1) throw new RangeError(E_EMPTYARRAY);
let base = this.dataStart;
let element = load(base);
let lastIndex = len - 1;
memory.copy(
base,
base + sizeof(),
lastIndex << alignof()
);
if (isReference()) {
store(base + (lastIndex << alignof()), 0);
} else {
// @ts-ignore
store(base + (lastIndex << alignof()), 0);
}
this.length_ = lastIndex;
return element;
}
some(fn: (value: T, index: i32, array: Array) => bool): bool {
for (let i = 0, len = this.length_; i < min(len, this.length_); ++i) {
if (fn(load(this.dataStart + (i << alignof())), i, this)) return true;
}
return false;
}
unshift(value: T): i32 {
let len = this.length_ + 1;
ensureCapacity(changetype(this), len, alignof());
let ptr = this.dataStart;
memory.copy(
ptr + sizeof(),
ptr,
(len - 1) << alignof()
);
store(ptr, value);
if (isManaged()) {
__link(changetype(this), changetype(value), true);
}
this.length_ = len;
return len;
}
slice(start: i32 = 0, end: i32 = i32.MAX_VALUE): Array {
let len = this.length_;
start = start < 0 ? max(start + len, 0) : min(start, len);
end = end < 0 ? max(end + len, 0) : min(end , len);
len = max(end - start, 0);
let slice = changetype>(__newArray(len, alignof(), idof>()));
let sliceBase = slice.dataStart;
let thisBase = this.dataStart + (start << alignof());
if (isManaged()) {
let off = 0;
let end = len << alignof();
while (off < end) {
let ref = load(thisBase + off);
store(sliceBase + off, ref);
__link(changetype(slice), ref, true);
off += sizeof();
}
} else {
memory.copy(sliceBase, thisBase, len << alignof());
}
return slice;
}
splice(start: i32, deleteCount: i32 = i32.MAX_VALUE): Array {
let len = this.length_;
start = start < 0 ? max(len + start, 0) : min(start, len);
deleteCount = max(min(deleteCount, len - start), 0);
let result = changetype>(__newArray(deleteCount, alignof(), idof>()));
let resultStart = result.dataStart;
let thisStart = this.dataStart;
let thisBase = thisStart + (start << alignof());
memory.copy(
resultStart,
thisBase,
deleteCount << alignof()
);
let offset = start + deleteCount;
if (len != offset) {
memory.copy(
thisBase,
thisStart + (offset << alignof()),
(len - offset) << alignof()
);
}
this.length_ = len - deleteCount;
return result;
}
reverse(): Array {
REVERSE(this.dataStart, this.length_);
return this;
}
sort(comparator: (a: T, b: T) => i32 = COMPARATOR()): Array {
SORT(this.dataStart, this.length_, comparator);
return this;
}
join(separator: string = ","): string {
let ptr = this.dataStart;
let len = this.length_;
if (isBoolean()) return joinBooleanArray(ptr, len, separator);
if (isInteger()) return joinIntegerArray(ptr, len, separator);
if (isFloat()) return joinFloatArray(ptr, len, separator);
if (ASC_SHRINK_LEVEL < 1) {
if (isString()) return joinStringArray(ptr, len, separator);
}
// For rest objects and arrays use general join routine
if (isReference()) return joinReferenceArray(ptr, len, separator);
ERROR("unspported element type");
return unreachable();
}
flat(): T {
if (!isArray()) {
ERROR("Cannot call flat() on Array where T is not an Array.");
}
// Get the length and data start values
let ptr = this.dataStart;
let len = this.length_;
// calculate the end size with an initial pass
let size = 0;
for (let i = 0; i < len; ++i) {
let child = load(ptr + (i << alignof()));
size += child == 0 ? 0 : load(child, offsetof("length_"));
}
// calculate the byteLength of the resulting backing ArrayBuffer
const align = alignof>();
let byteLength = size << align;
let outBuffer = changetype(__new(byteLength, idof()));
// create the return value and initialize it
let outArray = changetype(__new(offsetof