moonlight-delta-0.1.0.0: src-patch/Moonlight/Delta/Patch/Internal/Compose/Record.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
-- Loop-local rebinding (state/cursor/coverage) is the engine idiom here; shadowing is deliberate.
{-# OPTIONS_GHC -Wno-name-shadowing #-}
module Moonlight.Delta.Patch.Internal.Compose.Record
( recordApplied,
recordMany,
)
where
import Data.Foldable qualified as Foldable
import Data.Map.Strict qualified as Map
import Moonlight.Delta.Patch.Internal.Builder
( toPagedMap,
)
import Moonlight.Delta.Patch.Internal.Cell
( cellAfterEndpoint,
cellBeforeEndpoint,
cellFromEndpointPair,
endpointToMaybe,
)
import Moonlight.Delta.Patch.Internal.Compose.Range
import Moonlight.Delta.Patch.Internal.Construction
( fromAscList,
singleton,
)
import Moonlight.Delta.Patch.Internal.Page
import Moonlight.Delta.Patch.Internal.Types
import Prelude
data InsertCellResult key value = InsertCellResult !Bool ![(key, CellPatch value)]
recordApplied ::
forall key value.
(PatchKey key, PatchValue value) =>
key ->
CellPatch value ->
Patch key value ->
Either (ComposeError key value) (Patch key value)
recordApplied key latest patch =
case patch of
SmallPatch cells ->
fmap fromAscList (insertOrReplaceSmall (smallCellsToAscList cells))
PagedPatch entryCount pages ->
case pageForInsertion key pages of
Nothing ->
Right (singleton key latest)
Just (maximumKey, page) ->
case pageLookupIndex key maximumKey page of
Just rowIndex ->
replaceRecordedRow entryCount pages maximumKey page rowIndex
Nothing ->
Right (insertRecordedRow entryCount pages maximumKey page)
where
!latestBefore = cellBeforeEndpoint latest
!latestAfter = cellAfterEndpoint latest
insertOrReplaceSmall [] =
Right [(key, latest)]
insertOrReplaceSmall (row@(rowKey, rowCell) : rest) =
case compare key rowKey of
LT ->
Right ((key, latest) : row : rest)
EQ ->
let !olderAfter = cellAfterEndpoint rowCell
in if olderAfter /= latestBefore
then
Left
ComposeBoundaryMismatch
{ boundaryKey = key,
olderAfter = endpointToMaybe olderAfter,
newerBefore = endpointToMaybe latestBefore
}
else
let !combined = cellFromEndpointPair (cellBeforeEndpoint rowCell) latestAfter
in Right ((key, combined) : rest)
GT ->
fmap (row :) (insertOrReplaceSmall rest)
replaceRecordedRow entryCount pages maximumKey page rowIndex =
let !olderAfter = cellAfterEndpoint (rowCellAt page rowIndex)
in if olderAfter /= latestBefore
then
Left
ComposeBoundaryMismatch
{ boundaryKey = key,
olderAfter = endpointToMaybe olderAfter,
newerBefore = endpointToMaybe latestBefore
}
else
let (!updatedMaximum, !updatedPage) =
replacePageEntryAfter key latestAfter rowIndex maximumKey page
in Right
(PagedPatch entryCount (replaceRecordedPage maximumKey updatedMaximum updatedPage pages))
insertRecordedRow entryCount pages maximumKey page =
let InsertCellResult inserted localRows =
insertCell (pageToAscCells maximumKey page)
!localPatch =
fromAscList localRows
(!leftPages, !_removedPage, !rightPages) =
Map.splitLookup maximumKey pages
in PagedPatch
( entryCount
+ if inserted
then 1
else 0
)
(appendPages leftPages (appendPages (toPagedMap localPatch) rightPages))
insertCell rows =
case rows of
[] ->
InsertCellResult True [(key, latest)]
row@(rowKey, _rowCell) : rest ->
case compare key rowKey of
LT ->
InsertCellResult True ((key, latest) : rows)
EQ ->
InsertCellResult False ((key, latest) : rest)
GT ->
let InsertCellResult inserted insertedRows = insertCell rest
in InsertCellResult inserted (row : insertedRows)
{-# INLINABLE recordApplied #-}
recordMany ::
forall edits key value.
(Foldable edits, PatchKey key, PatchValue value) =>
edits (key, CellPatch value) ->
Either (ComposeError key value) (Patch key value)
recordMany edits =
fmap temporalRowsToPatch (Foldable.foldlM recordOne Map.empty edits)
where
recordOne ::
Map.Map key (Endpoint value, Endpoint value) ->
(key, CellPatch value) ->
Either (ComposeError key value) (Map.Map key (Endpoint value, Endpoint value))
recordOne accumulated (key, patch) =
let !before = cellBeforeEndpoint patch
!after = cellAfterEndpoint patch
in case Map.lookup key accumulated of
Nothing ->
Right (Map.insert key (before, after) accumulated)
Just (initial, current)
| current == before ->
Right (Map.insert key (initial, after) accumulated)
| otherwise ->
Left
ComposeBoundaryMismatch
{ boundaryKey = key,
olderAfter = endpointToMaybe current,
newerBefore = endpointToMaybe before
}
temporalRowsToPatch ::
Map.Map key (Endpoint value, Endpoint value) ->
Patch key value
temporalRowsToPatch =
fromAscList
. fmap
( \(key, (initial, current)) ->
(key, cellFromEndpointPair initial current)
)
. Map.toAscList
{-# INLINABLE recordMany #-}