packages feed

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

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- Loop-local rebinding (state/cursor/coverage) is the engine idiom here; shadowing is deliberate.
{-# OPTIONS_GHC -Wno-name-shadowing #-}

module Moonlight.Delta.Patch.Internal.Cursor
  ( AscCursor (..),
    ascCursor,
    ascAdvance,
    Cursor (CursorEnd),
    cursor,
    fromAsc,
    advanceRow,
    advancePage,
    cursorPageStart,
    currentKey,
    currentRow,
    beforeMaybe,
    afterMaybe,
  )
where

import Data.Map.Internal qualified as MapInternal
import Data.Map.Strict (Map)
import Moonlight.Delta.Patch.Internal.Page
  ( ColumnView,
    advancePackedIndex,
    columnEndpointFromView,
    columnMaybeAt,
    columnView,
  )
import Moonlight.Delta.Patch.Internal.Types
  ( Endpoint,
    Page (..),
    pageKeyAt,
  )
import Prelude

data AscStack key value
  = AscStackEnd
  | AscStackNode !key value !(Map key value) !(AscStack key value)

data AscCursor key value
  = AscEnd
  | AscCursor !key value !(Map key value) !(AscStack key value)

ascCursor :: forall key value. Map key value -> AscCursor key value
ascCursor tree =
  descend tree AscStackEnd
  where
    descend :: Map key value -> AscStack key value -> AscCursor key value
    descend entries stack =
      case entries of
        MapInternal.Tip ->
          popStack stack
        MapInternal.Bin _ key value left right ->
          descend left (AscStackNode key value right stack)

    popStack :: AscStack key value -> AscCursor key value
    popStack stack =
      case stack of
        AscStackEnd ->
          AscEnd
        AscStackNode key value right rest ->
          AscCursor key value right rest
{-# INLINE ascCursor #-}

ascAdvance :: forall key value. AscCursor key value -> AscCursor key value
ascAdvance cursor =
  case cursor of
    AscEnd ->
      AscEnd
    AscCursor _key _value right stack ->
      descend right stack
  where
    descend :: Map key value -> AscStack key value -> AscCursor key value
    descend entries remaining =
      case entries of
        MapInternal.Tip ->
          popStack remaining
        MapInternal.Bin _ key value left nextRight ->
          descend left (AscStackNode key value nextRight remaining)

    popStack :: AscStack key value -> AscCursor key value
    popStack remaining =
      case remaining of
        AscStackEnd ->
          AscEnd
        AscStackNode key value nextRight rest ->
          AscCursor key value nextRight rest
{-# INLINE ascAdvance #-}

data PatchEndpointSide
  = PatchBefore
  | PatchAfter

data ColumnCursor (side :: PatchEndpointSide) value = ColumnCursor
  { columnCursorPackedIndex :: {-# UNPACK #-} !Int,
    columnCursorView :: {-# UNPACK #-} !(ColumnView value)
  }

data ActiveCursor key value = ActiveCursor
  { activeCursorMaximumKey :: !key,
    activeCursorPage :: !(Page key value),
    activeCursorLogicalIndex :: {-# UNPACK #-} !Int,
    activeCursorBefore :: {-# UNPACK #-} !(ColumnCursor 'PatchBefore value),
    activeCursorAfter :: {-# UNPACK #-} !(ColumnCursor 'PatchAfter value),
    activeCursorRemainingPages :: !(AscCursor key (Page key value))
  }

data Cursor key value
  = CursorEnd
  | CursorActive {-# UNPACK #-} !(ActiveCursor key value)

cursor :: Map key (Page key value) -> Cursor key value
cursor =
  fromAsc . ascCursor
{-# INLINE cursor #-}

fromAsc :: AscCursor key (Page key value) -> Cursor key value
fromAsc cursor =
  case cursor of
    AscEnd ->
      CursorEnd
    AscCursor maximumKey page _right _stack ->
      CursorActive
        ActiveCursor
          { activeCursorMaximumKey = maximumKey,
            activeCursorPage = page,
            activeCursorLogicalIndex = 0,
            activeCursorBefore =
              ColumnCursor
                { columnCursorPackedIndex = 0,
                  columnCursorView = columnView (pageCount page) (pageBeforeColumn page)
                },
            activeCursorAfter =
              ColumnCursor
                { columnCursorPackedIndex = 0,
                  columnCursorView = columnView (pageCount page) (pageAfterColumn page)
                },
            activeCursorRemainingPages = ascAdvance cursor
          }
{-# INLINE fromAsc #-}

advancePage :: Cursor key value -> Cursor key value
advancePage cursor =
  case cursor of
    CursorEnd ->
      CursorEnd
    CursorActive activeCursor ->
      fromAsc (activeCursorRemainingPages activeCursor)
{-# INLINE advancePage #-}

advanceRow :: Cursor key value -> Cursor key value
advanceRow cursor =
  case cursor of
    CursorEnd ->
      CursorEnd
    CursorActive activeCursor ->
      let !logicalIndex = activeCursorLogicalIndex activeCursor
          !nextLogicalIndex = logicalIndex + 1
       in if nextLogicalIndex == pageCount (activeCursorPage activeCursor)
            then fromAsc (activeCursorRemainingPages activeCursor)
            else
              CursorActive
                activeCursor
                  { activeCursorLogicalIndex = nextLogicalIndex,
                    activeCursorBefore = advanceColumnCursor logicalIndex (activeCursorBefore activeCursor),
                    activeCursorAfter = advanceColumnCursor logicalIndex (activeCursorAfter activeCursor)
                  }
{-# INLINE advanceRow #-}

advanceColumnCursor :: Int -> ColumnCursor side value -> ColumnCursor side value
advanceColumnCursor logicalIndex columnCursor =
  columnCursor
    { columnCursorPackedIndex =
      advancePackedIndex
        (columnCursorView columnCursor)
        logicalIndex
        (columnCursorPackedIndex columnCursor)
    }
{-# INLINE advanceColumnCursor #-}

cursorPageStart :: Cursor key value -> Maybe (key, Page key value)
cursorPageStart cursor =
  case cursor of
    CursorEnd ->
      Nothing
    CursorActive activeCursor
      | activeCursorLogicalIndex activeCursor == 0 ->
          Just (activeCursorMaximumKey activeCursor, activeCursorPage activeCursor)
      | otherwise ->
          Nothing
{-# INLINE cursorPageStart #-}

currentKey :: Cursor key value -> Maybe key
currentKey cursor =
  case cursor of
    CursorEnd ->
      Nothing
    CursorActive activeCursor ->
      Just
        ( pageKeyAt
            (activeCursorMaximumKey activeCursor)
            (activeCursorPage activeCursor)
            (activeCursorLogicalIndex activeCursor)
        )
{-# INLINE currentKey #-}

currentRow :: Cursor key value -> Maybe (key, Endpoint value, Endpoint value)
currentRow cursor =
  case cursor of
    CursorEnd ->
      Nothing
    CursorActive activeCursor ->
      let !logicalIndex = activeCursorLogicalIndex activeCursor
       in Just
            ( pageKeyAt
                (activeCursorMaximumKey activeCursor)
                (activeCursorPage activeCursor)
                logicalIndex,
              columnCursorEndpoint logicalIndex (activeCursorBefore activeCursor),
              columnCursorEndpoint logicalIndex (activeCursorAfter activeCursor)
            )
{-# INLINE currentRow #-}

columnCursorEndpoint :: Int -> ColumnCursor side value -> Endpoint value
columnCursorEndpoint logicalIndex columnCursor =
  columnEndpointFromView
    (columnCursorView columnCursor)
    logicalIndex
    (columnCursorPackedIndex columnCursor)
{-# INLINE columnCursorEndpoint #-}

beforeMaybe :: Cursor key value -> Maybe value
beforeMaybe =
  cursorColumnValue activeCursorBefore
{-# INLINE beforeMaybe #-}

afterMaybe :: Cursor key value -> Maybe value
afterMaybe =
  cursorColumnValue activeCursorAfter
{-# INLINE afterMaybe #-}

cursorColumnValue :: (ActiveCursor key value -> ColumnCursor side value) -> Cursor key value -> Maybe value
cursorColumnValue selectColumn cursor =
  case cursor of
    CursorEnd ->
      Nothing
    CursorActive activeCursor ->
      columnCursorValue
        (activeCursorLogicalIndex activeCursor)
        (selectColumn activeCursor)
{-# INLINE cursorColumnValue #-}

columnCursorValue :: Int -> ColumnCursor side value -> Maybe value
columnCursorValue logicalIndex columnCursor =
  columnMaybeAt
    (columnCursorView columnCursor)
    logicalIndex
    (columnCursorPackedIndex columnCursor)
{-# INLINE columnCursorValue #-}