packages feed

moonlight-delta-0.1.0.0: src-patch/Moonlight/Delta/Patch/Internal/Construction.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DerivingStrategies #-}
{-# 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.Construction
  ( empty,
    singleton,
    fromList,
    fromAscList,
    toAscList,
    lookup,
    mapMaybeWithKey,
    foldWithKey,
    foldWithKey',
    traverseWithKey,
    invert,
    diff,
  )
where

import Control.Monad.ST (ST, runST)
import Data.Bits (testBit)
import Data.Foldable (traverse_)
import Data.Map.Internal qualified as MapInternal
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Primitive.SmallArray
  ( SmallArray,
    emptySmallArray,
    indexSmallArray,
    mapSmallArray',
    sizeofSmallArray,
    smallArrayFromList,
  )
import Moonlight.Delta.Patch.Internal.Builder
import Moonlight.Delta.Patch.Internal.Cell
import Moonlight.Delta.Patch.Internal.Cursor
import Moonlight.Delta.Patch.Internal.Page
import Moonlight.Delta.Patch.Internal.Types
import Prelude hiding (lookup)

empty :: Patch key value
empty =
  SmallPatch emptySmallArray
{-# INLINE empty #-}

singleton :: key -> CellPatch value -> Patch key value
singleton key cell =
  SmallPatch (smallArrayFromList [Cell key cell])
{-# INLINE singleton #-}

fromList :: (PatchKey key, PatchValue value) => [(key, CellPatch value)] -> Patch key value
fromList =
  fromMapEntries . Map.fromList
{-# INLINABLE fromList #-}

fromAscList :: (PatchKey key, PatchValue value) => [(key, CellPatch value)] -> Patch key value
fromAscList entries =
  case consolidateAscending entries of
    Nothing ->
      fromList entries
    Just distinctEntries ->
      fromDistinctAscList distinctEntries
{-# INLINABLE fromAscList #-}

smallFromDistinctAscList :: [(key, CellPatch value)] -> Patch key value
smallFromDistinctAscList entries =
  SmallPatch (smallArrayFromList (fmap (uncurry Cell) entries))
{-# INLINE smallFromDistinctAscList #-}

entriesShorterThan :: Int -> [a] -> Bool
entriesShorterThan limit =
  go limit
  where
    go :: Int -> [entry] -> Bool
    go remaining entries
      | remaining <= 0 =
          False
      | otherwise =
          case entries of
            [] ->
              True
            _ : rest ->
              go (remaining - 1) rest
{-# INLINE entriesShorterThan #-}

consolidateAscending :: forall key value. Ord key => [(key, value)] -> Maybe [(key, value)]
consolidateAscending entries =
  case entries of
    [] ->
      Just []
    firstEntry : remainingEntries ->
      reverse <$> collect firstEntry remainingEntries []
  where
    collect :: (key, value) -> [(key, value)] -> [(key, value)] -> Maybe [(key, value)]
    collect currentEntry [] accumulated =
      Just (currentEntry : accumulated)
    collect currentEntry@(key, _value) (nextEntry@(nextKey, _nextValue) : remainingEntries) accumulated =
      case compare key nextKey of
        LT ->
          collect nextEntry remainingEntries (currentEntry : accumulated)
        EQ ->
          collect nextEntry remainingEntries accumulated
        GT ->
          Nothing
{-# INLINABLE consolidateAscending #-}

fromDistinctAscList :: (PatchKey key, PatchValue value) => [(key, CellPatch value)] -> Patch key value
fromDistinctAscList entries =
  if entriesShorterThan smallFormThreshold entries
    then smallFromDistinctAscList entries
    else runST $ do
      builder <- newBuilder
      traverse_ (uncurry (appendCell builder)) entries
      finishBuilder builder
{-# INLINABLE fromDistinctAscList #-}

fromMapEntries :: (PatchKey key, PatchValue value) => Map key (CellPatch value) -> Patch key value
fromMapEntries entries
  | Map.size entries < smallFormThreshold =
      smallFromDistinctAscList (Map.toAscList entries)
  | otherwise =
      runST $ do
        builder <- newBuilder
        appendMapCells builder entries
        finishBuilder builder
{-# INLINABLE fromMapEntries #-}

appendMapCells :: (PatchKey key, PatchValue value) => Builder s key value -> Map key (CellPatch value) -> ST s ()
appendMapCells builder entries =
  case entries of
    MapInternal.Tip ->
      pure ()
    MapInternal.Bin _treeSize key cell left right -> do
      appendMapCells builder left
      appendCell builder key cell
      appendMapCells builder right
{-# INLINABLE appendMapCells #-}

appendCell :: (PatchKey key, PatchValue value) => Builder s key value -> key -> CellPatch value -> ST s ()
appendCell builder key cell =
  appendTransition builder key (cellBeforeEndpoint cell) (cellAfterEndpoint cell)
{-# INLINE appendCell #-}

toAscList :: Patch key value -> [(key, CellPatch value)]
toAscList =
  foldWithKey
    (\key rest -> (key, AssertAbsent) : rest)
    (\key after rest -> (key, Insert after) : rest)
    (\key before rest -> (key, Delete before) : rest)
    (\key before after rest -> (key, Replace before after) : rest)
    []
{-# INLINE toAscList #-}

lookup :: Ord key => key -> Patch key value -> Maybe (CellPatch value)
lookup key patch =
  case patch of
    SmallPatch cells ->
      lookupSmall key cells 0 (sizeofSmallArray cells)
    PagedPatch _count pages ->
      case pageForKey key pages of
        Nothing ->
          Nothing
        Just (maximumKey, page) ->
          fmap (rowCellAt page) (pageLookupIndex key maximumKey page)
{-# INLINABLE lookup #-}

lookupSmall :: Ord key => key -> SmallArray (Cell key value) -> Int -> Int -> Maybe (CellPatch value)
lookupSmall key cells !index !count
  | index == count =
      Nothing
  | otherwise =
      case indexSmallArray cells index of
        Cell currentKey cell ->
          case compare key currentKey of
            LT ->
              Nothing
            EQ ->
              Just cell
            GT ->
              lookupSmall key cells (index + 1) count
{-# INLINABLE lookupSmall #-}

mapMaybeWithKey :: (key -> CellPatch value -> Maybe value') -> Patch key value -> Map key value'
mapMaybeWithKey project patch =
  Map.fromDistinctAscList
    ( foldWithKey
        (\key rest -> emit key AssertAbsent rest)
        (\key after rest -> emit key (Insert after) rest)
        (\key before rest -> emit key (Delete before) rest)
        (\key before after rest -> emit key (Replace before after) rest)
        []
        patch
    )
  where
    emit key cell rest =
      case project key cell of
        Nothing -> rest
        Just !projected -> (key, projected) : rest
{-# INLINE mapMaybeWithKey #-}

foldPageRight ::
  (key -> result -> result) ->
  (key -> value -> result -> result) ->
  (key -> value -> result -> result) ->
  (key -> value -> value -> result -> result) ->
  key ->
  Page key value ->
  result ->
  result
foldPageRight onAssertAbsent onInsert onDelete onReplace maximumKey page initial =
  case (columnView count beforeColumn, columnView count afterColumn) of
    (ColumnView beforeMask beforeValues, ColumnView afterMask afterValues) ->
      go beforeMask beforeValues afterMask afterValues 0 0 0
  where
    !count = pageCount page
    !beforeColumn = pageBeforeColumn page
    !afterColumn = pageAfterColumn page

    go !beforeMask !beforeValues !afterMask !afterValues !index !beforePacked !afterPacked
      | index == count =
          initial
      | otherwise =
          let !key = pageKeyAt maximumKey page index
              !beforePresent = testBit beforeMask index
              !afterPresent = testBit afterMask index
              !nextBeforePacked = if beforePresent then beforePacked + 1 else beforePacked
              !nextAfterPacked = if afterPresent then afterPacked + 1 else afterPacked
              rest = go beforeMask beforeValues afterMask afterValues (index + 1) nextBeforePacked nextAfterPacked
           in case (beforePresent, afterPresent) of
                (False, False) -> onAssertAbsent key rest
                (False, True) -> onInsert key (valueColumnAt afterValues afterPacked) rest
                (True, False) -> onDelete key (valueColumnAt beforeValues beforePacked) rest
                (True, True) -> onReplace key (valueColumnAt beforeValues beforePacked) (valueColumnAt afterValues afterPacked) rest
{-# INLINE foldPageRight #-}

foldPageLeftStrict ::
  (result -> key -> result) ->
  (result -> key -> value -> result) ->
  (result -> key -> value -> result) ->
  (result -> key -> value -> value -> result) ->
  result ->
  key ->
  Page key value ->
  result
foldPageLeftStrict onAssertAbsent onInsert onDelete onReplace initial maximumKey page =
  case (columnView count beforeColumn, columnView count afterColumn) of
    (ColumnView beforeMask beforeValues, ColumnView afterMask afterValues) ->
      go beforeMask beforeValues afterMask afterValues 0 0 0 initial
  where
    !count = pageCount page
    !beforeColumn = pageBeforeColumn page
    !afterColumn = pageAfterColumn page

    go !beforeMask !beforeValues !afterMask !afterValues !index !beforePacked !afterPacked !accumulator
      | index == count =
          accumulator
      | otherwise =
          let !key = pageKeyAt maximumKey page index
              !beforePresent = testBit beforeMask index
              !afterPresent = testBit afterMask index
              !nextAccumulator =
                case (beforePresent, afterPresent) of
                  (False, False) -> onAssertAbsent accumulator key
                  (False, True) -> onInsert accumulator key (valueColumnAt afterValues afterPacked)
                  (True, False) -> onDelete accumulator key (valueColumnAt beforeValues beforePacked)
                  (True, True) -> onReplace accumulator key (valueColumnAt beforeValues beforePacked) (valueColumnAt afterValues afterPacked)
              !nextBeforePacked = if beforePresent then beforePacked + 1 else beforePacked
              !nextAfterPacked = if afterPresent then afterPacked + 1 else afterPacked
           in go beforeMask beforeValues afterMask afterValues (index + 1) nextBeforePacked nextAfterPacked nextAccumulator
{-# INLINE foldPageLeftStrict #-}

foldWithKey ::
  (key -> result -> result) ->
  (key -> value -> result -> result) ->
  (key -> value -> result -> result) ->
  (key -> value -> value -> result -> result) ->
  result ->
  Patch key value ->
  result
foldWithKey onAssertAbsent onInsert onDelete onReplace initial patch =
  case patch of
    SmallPatch cells ->
      foldSmallRight onAssertAbsent onInsert onDelete onReplace initial cells
    PagedPatch _count pages ->
      Map.foldrWithKey (foldPageRight onAssertAbsent onInsert onDelete onReplace) initial pages
{-# INLINE foldWithKey #-}

foldSmallRight ::
  (key -> result -> result) ->
  (key -> value -> result -> result) ->
  (key -> value -> result -> result) ->
  (key -> value -> value -> result -> result) ->
  result ->
  SmallArray (Cell key value) ->
  result
foldSmallRight onAssertAbsent onInsert onDelete onReplace initial cells =
  go 0
  where
    !count = sizeofSmallArray cells
    go !index
      | index == count =
          initial
      | otherwise =
          case indexSmallArray cells index of
            Cell key cell ->
              let rest = go (index + 1)
               in case cell of
                    AssertAbsent ->
                      onAssertAbsent key rest
                    Insert after ->
                      onInsert key after rest
                    Delete before ->
                      onDelete key before rest
                    Replace before after ->
                      onReplace key before after rest
{-# INLINE foldSmallRight #-}

foldWithKey' ::
  (result -> key -> result) ->
  (result -> key -> value -> result) ->
  (result -> key -> value -> result) ->
  (result -> key -> value -> value -> result) ->
  result ->
  Patch key value ->
  result
foldWithKey' onAssertAbsent onInsert onDelete onReplace initial patch =
  case patch of
    SmallPatch cells ->
      foldSmallLeftStrict onAssertAbsent onInsert onDelete onReplace initial cells
    PagedPatch _count pages ->
      Map.foldlWithKey' (foldPageLeftStrict onAssertAbsent onInsert onDelete onReplace) initial pages
{-# INLINE foldWithKey' #-}

foldSmallLeftStrict ::
  (result -> key -> result) ->
  (result -> key -> value -> result) ->
  (result -> key -> value -> result) ->
  (result -> key -> value -> value -> result) ->
  result ->
  SmallArray (Cell key value) ->
  result
foldSmallLeftStrict onAssertAbsent onInsert onDelete onReplace initial cells =
  go 0 initial
  where
    !count = sizeofSmallArray cells
    go !index !accumulator
      | index == count =
          accumulator
      | otherwise =
          case indexSmallArray cells index of
            Cell key cell ->
              let !nextAccumulator =
                    case cell of
                      AssertAbsent ->
                        onAssertAbsent accumulator key
                      Insert after ->
                        onInsert accumulator key after
                      Delete before ->
                        onDelete accumulator key before
                      Replace before after ->
                        onReplace accumulator key before after
               in go (index + 1) nextAccumulator
{-# INLINE foldSmallLeftStrict #-}

traverseWithKey ::
  (Applicative effect, PatchKey key, PatchValue value') =>
  (key -> CellPatch value -> effect (CellPatch value')) ->
  Patch key value ->
  effect (Patch key value')
traverseWithKey step patch =
  fromAscList <$> traverse traverseCell (toAscList patch)
  where
    traverseCell (key, cell) =
      fmap (\nextCell -> (key, nextCell)) (step key cell)
{-# INLINE traverseWithKey #-}

invert :: Patch key value -> Patch key value
invert patch =
  case patch of
    SmallPatch cells ->
      SmallPatch (mapSmallArray' invertCell cells)
    PagedPatch count pages ->
      PagedPatch count (Map.map invertPage pages)
{-# INLINE invert #-}

invertCell :: Cell key value -> Cell key value
invertCell (Cell key cell) =
  Cell key (invertCellPatch cell)
{-# INLINE invertCell #-}

invertCellPatch :: CellPatch value -> CellPatch value
invertCellPatch cell =
  case cell of
    AssertAbsent ->
      AssertAbsent
    Insert after ->
      Delete after
    Delete before ->
      Insert before
    Replace before after ->
      Replace after before
{-# INLINE invertCellPatch #-}

diff :: forall key value. (PatchKey key, PatchValue value) => Map key value -> Map key value -> Patch key value
diff before after =
  normalize
    ( runST $ do
        builder <- newBuilder
        appendDiff builder (ascCursor before) (ascCursor after)
        finishBuilder builder
    )
  where
    appendDiff :: Builder s key value -> AscCursor key value -> AscCursor key value -> ST s ()
    appendDiff builder beforeCursor afterCursor =
      case (beforeCursor, afterCursor) of
        (AscEnd, AscEnd) ->
          pure ()
        (AscCursor beforeKey beforeValue _ _, AscEnd) -> do
          appendTransition builder beforeKey (EndpointPresent beforeValue) EndpointAbsent
          appendDiff builder (ascAdvance beforeCursor) AscEnd
        (AscEnd, AscCursor afterKey afterValue _ _) -> do
          appendTransition builder afterKey EndpointAbsent (EndpointPresent afterValue)
          appendDiff builder AscEnd (ascAdvance afterCursor)
        (AscCursor beforeKey beforeValue _ _, AscCursor afterKey afterValue _ _) ->
          case compare beforeKey afterKey of
            LT -> do
              appendTransition builder beforeKey (EndpointPresent beforeValue) EndpointAbsent
              appendDiff builder (ascAdvance beforeCursor) afterCursor
            GT -> do
              appendTransition builder afterKey EndpointAbsent (EndpointPresent afterValue)
              appendDiff builder beforeCursor (ascAdvance afterCursor)
            EQ -> do
              if beforeValue == afterValue
                then pure ()
                else appendTransition builder afterKey (EndpointPresent beforeValue) (EndpointPresent afterValue)
              appendDiff builder (ascAdvance beforeCursor) (ascAdvance afterCursor)
{-# INLINABLE diff #-}