import * as Chunk from "@effect/data/Chunk" import * as Data from "@effect/data/Data" import type * as Differ from "@effect/data/Differ" import * as Equal from "@effect/data/Equal" import * as Dual from "@effect/data/Function" import { pipe } from "@effect/data/Function" /** @internal */ export const ChunkPatchTypeId: Differ.Differ.Chunk.TypeId = Symbol.for( "@effect/data/DifferChunkPatch" ) as Differ.Differ.Chunk.TypeId function variance(a: A): B { return a as unknown as B } const PatchProto = Object.setPrototypeOf({ [ChunkPatchTypeId]: { _Value: variance, _Patch: variance } }, Data.Structural.prototype) interface Empty extends Differ.Differ.Chunk.Patch { readonly _tag: "Empty" } const EmptyProto = Object.setPrototypeOf({ _tag: "Empty" }, PatchProto) const _empty = Object.create(EmptyProto) /** * @internal */ export const empty = (): Differ.Differ.Chunk.Patch => _empty interface AndThen extends Differ.Differ.Chunk.Patch { readonly _tag: "AndThen" readonly first: Differ.Differ.Chunk.Patch readonly second: Differ.Differ.Chunk.Patch } const AndThenProto = Object.setPrototypeOf({ _tag: "AndThen" }, PatchProto) const makeAndThen = ( first: Differ.Differ.Chunk.Patch, second: Differ.Differ.Chunk.Patch ): Differ.Differ.Chunk.Patch => { const o = Object.create(AndThenProto) o.first = first o.second = second return o } interface Append extends Differ.Differ.Chunk.Patch { readonly _tag: "Append" readonly values: Chunk.Chunk } const AppendProto = Object.setPrototypeOf({ _tag: "Append" }, PatchProto) const makeAppend = (values: Chunk.Chunk): Differ.Differ.Chunk.Patch => { const o = Object.create(AppendProto) o.values = values return o } interface Slice extends Differ.Differ.Chunk.Patch { readonly _tag: "Slice" readonly from: number readonly until: number } const SliceProto = Object.setPrototypeOf({ _tag: "Slice" }, PatchProto) const makeSlice = (from: number, until: number): Differ.Differ.Chunk.Patch => { const o = Object.create(SliceProto) o.from = from o.until = until return o } interface Update extends Differ.Differ.Chunk.Patch { readonly _tag: "Update" readonly index: number readonly patch: Patch } const UpdateProto = Object.setPrototypeOf({ _tag: "Update" }, PatchProto) const makeUpdate = (index: number, patch: Patch): Differ.Differ.Chunk.Patch => { const o = Object.create(UpdateProto) o.index = index o.patch = patch return o } type Instruction = | Empty | AndThen | Append | Slice | Update /** @internal */ export const diff = ( options: { readonly oldValue: Chunk.Chunk readonly newValue: Chunk.Chunk readonly differ: Differ.Differ } ): Differ.Differ.Chunk.Patch => { let i = 0 let patch = empty() while (i < options.oldValue.length && i < options.newValue.length) { const oldElement = Chunk.unsafeGet(i)(options.oldValue) const newElement = Chunk.unsafeGet(i)(options.newValue) const valuePatch = options.differ.diff(oldElement, newElement) if (!Equal.equals(valuePatch, options.differ.empty)) { patch = pipe(patch, combine(makeUpdate(i, valuePatch))) } i = i + 1 } if (i < options.oldValue.length) { patch = pipe(patch, combine(makeSlice(0, i))) } if (i < options.newValue.length) { patch = pipe(patch, combine(makeAppend(Chunk.drop(i)(options.newValue)))) } return patch } /** @internal */ export const combine = Dual.dual< ( that: Differ.Differ.Chunk.Patch ) => ( self: Differ.Differ.Chunk.Patch ) => Differ.Differ.Chunk.Patch, ( self: Differ.Differ.Chunk.Patch, that: Differ.Differ.Chunk.Patch ) => Differ.Differ.Chunk.Patch >(2, (self, that) => makeAndThen(self, that)) /** @internal */ export const patch = Dual.dual< ( oldValue: Chunk.Chunk, differ: Differ.Differ ) => (self: Differ.Differ.Chunk.Patch) => Chunk.Chunk, ( self: Differ.Differ.Chunk.Patch, oldValue: Chunk.Chunk, differ: Differ.Differ ) => Chunk.Chunk >(3, ( self: Differ.Differ.Chunk.Patch, oldValue: Chunk.Chunk, differ: Differ.Differ ) => { let chunk = oldValue let patches: Chunk.Chunk> = Chunk.of(self) while (Chunk.isNonEmpty(patches)) { const head: Instruction = Chunk.headNonEmpty(patches) as Instruction const tail = Chunk.tailNonEmpty(patches) switch (head._tag) { case "Empty": { patches = tail break } case "AndThen": { patches = Chunk.prepend(head.first)(Chunk.prepend(head.second)(tail)) break } case "Append": { chunk = Chunk.appendAll(head.values)(chunk) patches = tail break } case "Slice": { const array = Chunk.toReadonlyArray(chunk) chunk = Chunk.unsafeFromArray(array.slice(head.from, head.until)) patches = tail break } case "Update": { const array = Chunk.toReadonlyArray(chunk) as Array array[head.index] = differ.patch(head.patch, array[head.index]!) chunk = Chunk.unsafeFromArray(array) patches = tail break } } } return chunk })