export const BufferSize = 64
export const ChunkTypeId = Symbol.for("@tsplus/collections/Chunk")
export type ChunkTypeId = typeof ChunkTypeId
export const alloc =
// @ts-ignore
typeof Buffer !== "undefined" ? Buffer.alloc : (n: number) => new Uint8Array(n)
export function isByte(u: unknown) {
return typeof u === "number" && Number.isInteger(u) && u >= 0 && u <= 255
}
export type IterableArrayLike = ArrayLike & Iterable
/**
* A `Chunk` represents a chunk of values of type `A`. Chunks are usually
* backed by arrays, but expose a purely functional, safe interface
* to the underlying elements, and they become lazy on operations that would be
* costly with arrays, such as repeated concatenation.
*
* The implementation of balanced concatenation is based on the one for
* Conc-Trees in "Conc-Trees for Functional and Parallel Programming" by
* Aleksandar Prokopec and Martin Odersky.
*
* http://aleksandar-prokopec.com/resources/docs/lcpc-conc-trees.pdf
*
* @tsplus type Chunk
*/
export interface Chunk extends Collection {
readonly [ChunkTypeId]: ChunkTypeId
readonly length: number
[Symbol.iterator](): Iterator
}
export interface ChunkF extends HKT {
readonly type: Chunk
}
export declare namespace Chunk {
export type HKT = ChunkF
}
/**
* @tsplus type Chunk.Ops
*/
export interface ChunkOps {
$: ChunkAspects
}
export const Chunk: ChunkOps = {
$: {}
}
/**
* @tsplus type Chunk.Aspects
*/
export interface ChunkAspects {}
/**
* @tsplus unify Chunk
*/
export function unifyChunk>(
self: X
): Chunk<[X] extends [Chunk] ? A : never> {
return self
}
/**
* Internal base class
*/
export abstract class ChunkInternal implements Chunk, Equals {
readonly [ChunkTypeId]: ChunkTypeId = ChunkTypeId
abstract readonly binary: boolean
abstract readonly length: number
abstract readonly depth: number
abstract readonly left: ChunkInternal
abstract readonly right: ChunkInternal
abstract _copyToArray(n: number, array: Array | Uint8Array): void
abstract _get(n: number): A
protected arrayLikeCache: IterableArrayLike | undefined
_arrayLike(): IterableArrayLike {
if (this.arrayLikeCache) {
return this.arrayLikeCache as IterableArrayLike
}
const arr = this.binary ? alloc(this.length) : new Array(this.length)
this._copyToArray(0, arr)
this.arrayLikeCache = arr
return arr as IterableArrayLike
}
private arrayCache: readonly unknown[] | undefined
_array(): readonly A[] {
if (this.arrayCache) {
return this.arrayCache as readonly A[]
}
const arr = new Array(this.length)
this._copyToArray(0, arr)
this.arrayCache = arr
return arr
}
[Equals.sym](that: unknown): boolean {
return isChunk(that) && corresponds_(this, that, Equals.equals)
}
[Hash.sym](): number {
return Hash.iterator(this[Symbol.iterator]())
}
toString() {
return `Chunk(${this._array().join(", ")})`
}
toJSON() {
return this._array()
}
abstract [Symbol.iterator](): Iterator
abstract _arrayLikeIterator(): Iterator>
abstract _reverseArrayLikeIterator(): Iterator>
_buckets(): Iterable> {
return {
[Symbol.iterator]: () => this._arrayLikeIterator()
}
}
_reverseBuckets(): Iterable> {
return {
[Symbol.iterator]: () => this._reverseArrayLikeIterator()
}
}
_reverse(): Iterable {
const arr = this._arrayLike()
return {
[Symbol.iterator]: () => {
let i = arr.length - 1
return {
next: () => {
if (i >= 0 && i < arr.length) {
const k = arr[i]!
i--
return {
value: k,
done: false
}
}
return {
value: arr.length,
done: true
}
}
}
}
}
}
_materialize(): ChunkInternal {
concreteChunk(this)
switch (this._typeId) {
case EmptyTypeId: {
return this
}
case ArrTypeId: {
return this
}
default: {
return array_(this._arrayLike())
}
}
}
_append(a1: A1): ChunkInternal {
const binary = this.binary && isByte(a1)
const buffer = this.binary && binary ? alloc(BufferSize) : new Array(BufferSize)
buffer[0] = a1
return new AppendN(this, buffer, 1, new AtomicNumber(1), this.binary && binary)
}
_prepend(a1: A1): ChunkInternal {
const binary = this.binary && isByte(a1)
const buffer = this.binary && binary ? alloc(BufferSize) : new Array(BufferSize)
buffer[BufferSize - 1] = a1
return new PrependN(this, buffer, 1, new AtomicNumber(1), this.binary && binary)
}
_take(n: number): ChunkInternal {
concreteChunk(this)
if (n <= 0) {
return _Empty
} else if (n >= this.length) {
return this
} else {
switch (this._typeId) {
case EmptyTypeId: {
return _Empty
}
case SliceTypeId: {
if (n >= this.length) {
return this
} else {
return new Slice(this.chunk, this.offset, n)
}
}
case SingletonTypeId: {
return this
}
default: {
return new Slice(this, 0, n)
}
}
}
}
_concat(that: ChunkInternal): ChunkInternal {
concreteChunk(this)
concreteChunk(that)
if (this._typeId === EmptyTypeId) {
return that
}
if (that._typeId === EmptyTypeId) {
return this
}
if (this._typeId === AppendNTypeId) {
const chunk = array_(this.buffer as A1[])._take(this.bufferUsed)
return this.start._concat(chunk)._concat(that)
}
if (that._typeId === PrependNTypeId) {
const chunk = array_(
that.bufferUsed === 0 ? [] : (that.buffer as A1[]).slice(-that.bufferUsed)
)
return this._concat(chunk)._concat(that.end)
}
const diff = that.depth - this.depth
if (Math.abs(diff) <= 1) {
return new Concat(this, that)
} else if (diff < -1) {
if (this.left.depth >= this.right.depth) {
const nr = this.right._concat(that)
return new Concat(this.left, nr)
} else {
const nrr = this.right.right._concat(that)
if (nrr.depth === this.depth - 3) {
const nr = new Concat(this.right.left, nrr)
return new Concat(this.left, nr)
} else {
const nl = new Concat(this.left, this.right.left)
return new Concat(nl, nrr)
}
}
} else {
if (this.right.depth >= that.left.depth) {
const nl = this._concat(that.left)
return new Concat(nl, that.right)
} else {
const nll = this._concat(that.left.left)
if (nll.depth === that.depth - 3) {
const nl = new Concat(nll, that.left.right)
return new Concat(nl, that.right)
} else {
const nr = new Concat(that.left.right, that.right)
return new Concat(nll, nr)
}
}
}
}
}
export const EmptyTypeId = Symbol.for(
"@effect-ts/core/collection/immutable/Chunk/Empty"
)
export type EmptyTypeId = typeof EmptyTypeId
/**
* Internal Empty Chunk
*/
export class Empty extends ChunkInternal {
readonly depth = 0
readonly _typeId: EmptyTypeId = EmptyTypeId
readonly left = this
readonly right = this
readonly binary = true
readonly length = 0
_get(n: number): A {
throw new IndexOutOfBounds(n, 0, this.length - 1)
}
constructor() {
super()
}
_materialize() {
return array_([])
}
_copyToArray(_n: number, _array: Array | Uint8Array) {
// no-op
}
[Symbol.iterator](): Iterator {
return {
next: () => ({
value: 0,
done: true
})
}
}
_arrayLikeIterator(): Iterator> {
return {
next: () => ({
value: 0,
done: true
})
}
}
_reverseArrayLikeIterator(): Iterator> {
return {
next: () => ({
value: 0,
done: true
})
}
}
}
export const _Empty: ChunkInternal = new Empty()
/**
* @tsplus macro remove
*/
export function concreteChunk(
_: Chunk
): asserts _ is
| Empty
| AppendN
| Arr
| Slice
| Singleton
| PrependN
| Concat
{
//
}
/**
* @tsplus macro identity
*/
export function concreteChunkId(
_: Chunk
): Empty | AppendN | Arr | Slice | Singleton | PrependN | Concat {
concreteChunk(_)
return _
}
export const AppendNTypeId = Symbol.for(
"@effect-ts/core/collection/immutable/Chunk/AppendN"
)
export type AppendNTypeId = typeof AppendNTypeId
/**
* Internal Append Chunk
*/
export class AppendN extends ChunkInternal {
readonly _typeId: AppendNTypeId = AppendNTypeId
readonly depth = 0
readonly left = _Empty
readonly right = _Empty
readonly length: number
constructor(
readonly start: ChunkInternal,
readonly buffer: Array | Uint8Array,
readonly bufferUsed: number,
readonly chain: AtomicNumber,
readonly binary: boolean
) {
super()
this.length = this.start.length + this.bufferUsed
}
_get(n: number): A {
if (n < this.start.length) {
return this.start._get(n)
}
const k = n - this.start.length
if (k >= this.buffer.length || k < 0) {
throw new IndexOutOfBounds(n, 0, this.length - 1)
}
return (this.buffer as A[])[k]!
}
_append(a1: A1): ChunkInternal {
const binary = this.binary && isByte(a1)
if (
this.bufferUsed < this.buffer.length &&
this.chain.compareAndSet(this.bufferUsed, this.bufferUsed + 1)
) {
if (this.binary && !binary) {
const buffer = new Array(BufferSize)
for (let i = 0; i < BufferSize; i++) {
buffer[i] = this.buffer[i]
}
buffer[this.bufferUsed] = a1
return new AppendN(
this.start,
buffer,
this.bufferUsed + 1,
this.chain,
this.binary && binary
)
}
this.buffer[this.bufferUsed] = a1
return new AppendN(
this.start,
this.buffer,
this.bufferUsed + 1,
this.chain,
this.binary && binary
)
} else {
const buffer = this.binary && binary ? alloc(BufferSize) : new Array(BufferSize)
buffer[0] = a1
const chunk = array_(this.buffer as A1[])._take(this.bufferUsed)
return new AppendN(
this.start._concat(chunk),
buffer,
1,
new AtomicNumber(1),
this.binary && binary
)
}
}
_copyToArray(n: number, array: Array | Uint8Array) {
this.start._copyToArray(n, array)
_copy(this.buffer as A[], 0, array, this.start.length + n, this.bufferUsed)
}
[Symbol.iterator](): Iterator {
const k = this._arrayLike()
return k[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
const array = this._arrayLike()
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
const array = this._arrayLike()
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
}
export const ArrTypeId = Symbol.for("@effect-ts/core/collection/immutable/Chunk/Arr")
export type ArrTypeId = typeof ArrTypeId
/**
* Internal Array Chunk
*/
export abstract class Arr extends ChunkInternal {
readonly _typeId: ArrTypeId = ArrTypeId
}
/**
* Internal Plain Array Chunk
*/
export class PlainArr extends Arr {
readonly depth = 0
readonly left = _Empty
readonly right = _Empty
readonly length: number
private isBytes?: boolean
constructor(readonly array: readonly A[]) {
super()
this.length = array.length
}
get binary(): boolean {
if (typeof this.isBytes !== "undefined") {
return this.isBytes
}
this.isBytes = this.array.every(isByte)
return this.isBytes
}
_get(n: number): A {
if (n >= this.length || n < 0) {
throw new IndexOutOfBounds(n, 0, this.length - 1)
}
return this.array[n]!
}
_arrayLike() {
if (!this.binary) {
return this.array
}
if (this.arrayLikeCache) {
return this.arrayLikeCache as IterableArrayLike
}
const arr = alloc(this.length)
this._copyToArray(0, arr)
this.arrayLikeCache = arr
return arr as unknown as IterableArrayLike
}
_array() {
return this.array
}
_materialize() {
return this
}
_copyToArray(n: number, array: Array | Uint8Array) {
_copy(this.array, 0, array, n, this.length)
}
[Symbol.iterator](): Iterator {
return this.array[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this.array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this.array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
}
/**
* Internal Binary Array Chunk
*/
export class Uint8Arr extends Arr {
readonly depth = 0
readonly left = _Empty
readonly right = _Empty
readonly length: number
readonly binary = true
constructor(readonly array: Uint8Array) {
super()
this.length = array.length
}
_arrayLike() {
return this.array
}
_get(n: number): number {
if (n >= this.length || n < 0) {
throw new IndexOutOfBounds(n, 0, this.length - 1)
}
return this.array[n]!
}
_materialize() {
return this
}
_copyToArray(n: number, array: Array | Uint8Array) {
_copy(this.array, 0, array, n, this.length)
}
[Symbol.iterator](): Iterator {
return this.array[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this.array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this.array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
}
export const SliceTypeId = Symbol.for(
"@effect-ts/core/collection/immutable/Chunk/Slice"
)
export type SliceTypeId = typeof SliceTypeId
/**
* Internal Slice Chunk
*/
export class Slice extends ChunkInternal {
readonly depth = 0
readonly left = _Empty
readonly right = _Empty
readonly binary: boolean
readonly _typeId: SliceTypeId = SliceTypeId
_get(n: number): A {
return this.chunk._get(n + this.offset)
}
constructor(
readonly chunk: ChunkInternal,
readonly offset: number,
readonly length: number
) {
super()
this.binary = this.chunk.binary
}
_copyToArray(n: number, array: Array | Uint8Array) {
let i = 0
let j = n
while (i < this.length) {
array[j] = this._get(i)!
i += 1
j += 1
}
}
[Symbol.iterator](): Iterator {
const k = this._arrayLike()
return k[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
const array = this._arrayLike()
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
const array = this._arrayLike()
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: array,
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
}
export const SingletonTypeId = Symbol.for(
"@effect-ts/core/collection/immutable/Chunk/Singleton"
)
export type SingletonTypeId = typeof SingletonTypeId
/**
* Internal Singleton Chunk
*/
export class Singleton extends ChunkInternal {
readonly depth = 0
readonly left = _Empty
readonly right = _Empty
readonly length = 1
readonly _typeId: SingletonTypeId = SingletonTypeId
_get(n: number): A {
if (n === 0) {
return this.a
}
throw new IndexOutOfBounds(n, 0, this.length - 1)
}
readonly binary: boolean
constructor(readonly a: A) {
super()
this.binary = isByte(a)
}
_copyToArray(n: number, array: Array | Uint8Array) {
array[n] = this.a
}
[Symbol.iterator](): Iterator {
const k = this._arrayLike()
return k[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this._arrayLike(),
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this._arrayLike(),
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
}
export const PrependNTypeId = Symbol.for(
"@effect-ts/core/collection/immutable/Chunk/PrependN"
)
export type PrependNTypeId = typeof PrependNTypeId
/**
* Internal Prepend Chunk
*/
export class PrependN extends ChunkInternal {
readonly depth = 0
readonly left = _Empty
readonly right = _Empty
readonly length: number
readonly _typeId: PrependNTypeId = PrependNTypeId
_get(n: number): A {
if (n < this.bufferUsed) {
const k = BufferSize - this.bufferUsed + n
if (k >= this.buffer.length || k < 0) {
throw new IndexOutOfBounds(n, 0, this.length - 1)
}
return (this.buffer as A[])[k]!
}
return this.end._get(n - this.bufferUsed)
}
constructor(
readonly end: ChunkInternal,
readonly buffer: Array | Uint8Array,
readonly bufferUsed: number,
readonly chain: AtomicNumber,
readonly binary: boolean
) {
super()
this.length = this.end.length + this.bufferUsed
}
_copyToArray(n: number, array: Array | Uint8Array) {
const length = Math.min(this.bufferUsed, Math.max(array.length - n, 0))
_copy(this.buffer, BufferSize - this.bufferUsed, array, n, length)
this.end._copyToArray(n + length, array)
}
prepend(a1: A1): ChunkInternal {
const binary = this.binary && isByte(a1)
if (
this.bufferUsed < this.buffer.length &&
this.chain.compareAndSet(this.bufferUsed, this.bufferUsed + 1)
) {
if (this.binary && !binary) {
const buffer = new Array(BufferSize)
for (let i = 0; i < BufferSize; i++) {
buffer[i] = this.buffer[i]
}
buffer[BufferSize - this.bufferUsed - 1] = a1
return new PrependN(this.end, buffer, this.bufferUsed + 1, this.chain, false)
}
this.buffer[BufferSize - this.bufferUsed - 1] = a1
return new PrependN(
this.end,
this.buffer,
this.bufferUsed + 1,
this.chain,
this.binary && binary
)
} else {
const buffer = binary ? alloc(BufferSize) : new Array(BufferSize)
buffer[BufferSize - 1] = a1
const chunk = array_(
"subarray" in this.buffer
? this.buffer.subarray(this.buffer.length - this.bufferUsed)
: this.buffer.slice(this.buffer.length - this.bufferUsed)
) as ChunkInternal
return new PrependN(
chunk._concat(this.end),
buffer,
1,
new AtomicNumber(1),
this.binary && binary
)
}
}
[Symbol.iterator](): Iterator {
const k = this._arrayLike()
return k[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this._arrayLike(),
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
let done = false
return {
next: () => {
if (!done) {
done = true
return {
value: this._arrayLike(),
done: false
}
} else {
return {
value: 1,
done: true
}
}
}
}
}
}
/**
* Internal copy arrays
*/
export function _copy(
src: IterableArrayLike,
srcPos: number,
dest: A[] | Uint8Array,
destPos: number,
len: number
) {
for (let i = srcPos; i < Math.min(src.length, srcPos + len); i++) {
dest[destPos + i - srcPos] = src[i]!
}
return dest
}
export const ConcatTypeId = Symbol.for(
"@effect-ts/core/collection/immutable/Chunk/Concat"
)
export type ConcatTypeId = typeof ConcatTypeId
/**
* Internal Concat Chunk
*/
export class Concat extends ChunkInternal {
readonly depth: number
readonly _typeId: ConcatTypeId = ConcatTypeId
readonly length: number
readonly binary: boolean
_get(n: number): A {
return n < this.left.length
? this.left._get(n)
: this.right._get(n - this.left.length)
}
constructor(readonly left: ChunkInternal, readonly right: ChunkInternal) {
super()
this.depth = 1 + Math.max(this.left.depth, this.right.depth)
this.length = this.left.length + this.right.length
this.binary = this.left.binary && this.right.binary
}
_copyToArray(n: number, array: Array | Uint8Array) {
this.left._copyToArray(n, array)
this.right._copyToArray(n + this.left.length, array)
}
[Symbol.iterator](): Iterator {
const k = this._arrayLike()
return k[Symbol.iterator]()
}
_arrayLikeIterator(): Iterator> {
let it = this.left._arrayLikeIterator()
let i = 0
let n = it.next()
let j = 0
return {
next: () => {
j++
if (i === 0 && n.done) {
it = this.right._arrayLikeIterator()
const k = it.next()
if (k.done) {
return {
value: j,
done: true
}
}
i++
n = it.next()
return k
} else {
if (n.done) {
return {
value: j,
done: true
}
}
const k = n
n = it.next()
return k
}
}
}
}
_reverseArrayLikeIterator(): Iterator> {
let it = this.right._arrayLikeIterator()
let i = 0
let n = it.next()
let j = 0
return {
next: () => {
j++
if (i === 0 && n.done) {
it = this.left._arrayLikeIterator()
const k = it.next()
if (k.done) {
return {
value: j,
done: true
}
}
i++
n = it.next()
return k
} else {
if (n.done) {
return {
value: j,
done: true
}
}
const k = n
n = it.next()
return k
}
}
}
}
}
/**
* Type guard
*
* @tsplus static Chunk.Ops isChunk
*/
export function isChunk(u: Iterable): u is Chunk
export function isChunk(u: unknown): u is Chunk
export function isChunk(u: unknown): u is Chunk {
return typeof u === "object" && u != null && ChunkTypeId in u
}
/**
* Internal Array Chunk Constructor
*/
function array_(array: Iterable): ChunkInternal
function array_(
array: Uint8Array | Iterable | IterableArrayLike
): ChunkInternal {
if (isChunk(array)) {
concreteChunk(array)
return array
}
if (array instanceof Uint8Array) {
return new Uint8Arr(array)
}
return new PlainArr(Array.isArray(array) ? array : Array.from(array))
}
/**
* Builds a chunk from an array.
*
* @tsplus static Chunk.Ops from
*/
export function from(array: Iterable): Chunk {
return array_(array)
}
/**
* Determines whether this chunk and the specified chunk have the same length
* and every pair of corresponding elements of this chunk and the specified
* chunk satisfy the specified predicate.
*
* @tsplus fluent Chunk corresponds
*/
export function corresponds_(
self: Chunk,
that: Chunk,
f: (a: A, b: B) => boolean
): boolean {
if (concreteChunkId(self).length !== concreteChunkId(that).length) {
return false
}
const leftIterator = concreteChunkId(self)._arrayLikeIterator()
const rightIterator = concreteChunkId(that)._arrayLikeIterator()
let i = 0
let j = 0
let equal = true
let done = false
let leftLength = 0
let rightLength = 0
let left: IterableArrayLike | undefined = undefined
let right: IterableArrayLike | undefined = undefined
let leftNext
let rightNext
while (equal && !done) {
if (i < leftLength && j < rightLength) {
if (!f(left![i]!, right![j]!)) {
equal = false
}
i++
j++
} else if (i === leftLength && (leftNext = leftIterator.next()) && !leftNext.done) {
left = leftNext.value
leftLength = left.length
i = 0
} else if (
j === rightLength &&
(rightNext = rightIterator.next()) &&
!rightNext.done
) {
right = rightNext.value
rightLength = right.length
j = 0
} else if (i === leftLength && j === rightLength) {
done = true
} else {
equal = false
}
}
return equal
}
/**
* Determines whether this chunk and the specified chunk have the same length
* and every pair of corresponding elements of this chunk and the specified
* chunk satisfy the specified predicate.
*
* @tsplus static Chunk.Aspects corresponds
*/
export const corresponds = Pipeable(corresponds_)