packages feed

moonlight-pale-0.1.0.0: src-ghc-surface/Moonlight/Pale/Ghc/Expr/Scope.hs

module Moonlight.Pale.Ghc.Expr.Scope
  ( ScopeId,
    ScopeIdFailure (..),
    ScopeCtx (..),
    ScopeIndex,
    ScopeIndexFailure (..),
    ScopeLookupFailure (..),
    FreeScopeSummary,
    mkScopeId,
    scopeIdKey,
    rootScopeId,
    mkScopeIndex,
    scopeIndexRoot,
    scopeParentId,
    scopeDepthOf,
    scopeIsAncestorOf,
    scopeComparable,
    scopeLca,
    scopeCtxLeq,
    scopeCtxMeet,
    scopeCtxJoin,
    scopeObservedCount,
    scopeObservedContexts,
    scopeTopCtx,
    scopeBottomCtx,
    binderIntroScope,
    binderSiteScope,
    emptyFreeScopeSummary,
    singletonFreeScopeSummary,
    mergeFreeScopeSummary,
    mergeFreeScopeSummaryBy,
    mergeFreeScopeSummaryByEither,
    deleteFreeScopeSummary,
    freeScopeSummaryContains,
    freeScopeSummarySize,
    freeScopeSummaryToList,
    freeScopeSupportAnchor,
  )
where

import Control.Monad (foldM, when)
import Data.Foldable (traverse_)
import Data.IntMap.Strict qualified as IntMap
import Data.Kind (Type)
import Data.Primitive.SmallArray
  ( SmallArray,
    indexSmallArray,
    sizeofSmallArray,
    smallArrayFromList,
  )
import Data.Vector (Vector)
import Data.Vector qualified as V
import Data.Void (absurd)
import Moonlight.Core (BinderId (..), binderIdKey)

type ScopeId :: Type
newtype ScopeId = ScopeId Int
  deriving stock (Eq, Ord, Show)

type ScopeIndex :: Type
data ScopeIndex = ScopeIndex
  { siParent :: !(Vector Int),
    siDepth :: !(Vector Int),
    siSubtreeEnd :: !(Vector Int),
    siLift :: !(Vector (Vector Int)),
    siHasBranch :: !Bool,
    siDeepest :: !ScopeId,
    siRoot :: !ScopeId,
    siBinderIntro :: !(Vector ScopeId)
  }
  deriving stock (Eq, Ord, Show)

type FreeScopeSummary :: Type
newtype FreeScopeSummary = FreeScopeSummary (SmallArray ScopeId)

instance Eq FreeScopeSummary where
  FreeScopeSummary leftArray == FreeScopeSummary rightArray =
    scopeArrayToList leftArray == scopeArrayToList rightArray

instance Ord FreeScopeSummary where
  compare (FreeScopeSummary leftArray) (FreeScopeSummary rightArray) =
    compare (scopeArrayToList leftArray) (scopeArrayToList rightArray)

instance Show FreeScopeSummary where
  showsPrec precedence (FreeScopeSummary scopeArray) =
    showParen
      (precedence > 10)
      (showString "FreeScopeSummary " . shows (scopeArrayToList scopeArray))

scopeArrayToList :: SmallArray ScopeId -> [ScopeId]
scopeArrayToList scopeArray =
  fmap (indexSmallArray scopeArray) [0 .. sizeofSmallArray scopeArray - 1]

type ScopeIdFailure :: Type
data ScopeIdFailure
  = NegativeScopeId !Int
  deriving stock (Eq, Ord, Show)

type ScopeCtx :: Type
data ScopeCtx
  = ActualScope !ScopeId
  | IncompatibleScope
  deriving stock (Eq, Ord, Show)

type ScopeLookupFailure :: Type
data ScopeLookupFailure
  = ScopeIdOutsideIndex !ScopeId !Int
  | BinderIdOutsideIndex !BinderId !Int
  | ScopeLiftLevelOutsideIndex !Int !Int
  deriving stock (Eq, Ord, Show)

type ScopeIndexFailure :: Type
data ScopeIndexFailure
  = ScopeIndexEmpty
  | ScopeRootParentInvalid !Int
  | ScopeParentEdgeInvalid !ScopeId !Int
  | ScopeBinderIntroInvalid !BinderId !Int
  | ScopeSubtreeEndMissing !ScopeId
  | ScopeLiftMissing !ScopeId
  | ScopeParentNotPreorder !ScopeId !Int
  deriving stock (Eq, Ord, Show)

mkScopeId :: Int -> Either ScopeIdFailure ScopeId
mkScopeId scopeKey
  | scopeKey < 0 =
      Left (NegativeScopeId scopeKey)
  | otherwise =
      Right (ScopeId scopeKey)

scopeIdKey :: ScopeId -> Int
scopeIdKey (ScopeId scopeKey) =
  scopeKey

rootScopeId :: ScopeId
rootScopeId =
  ScopeId 0

mkScopeIndex :: Vector Int -> Vector Int -> Either ScopeIndexFailure ScopeIndex
mkScopeIndex parentVector binderIntroVector = do
  case V.toList parentVector of
    [] ->
      Left ScopeIndexEmpty
    rootParent : _ ->
      when (rootParent /= 0) (Left (ScopeRootParentInvalid rootParent))
  traverse_ validateParentEdge (zip [1 ..] (drop 1 (V.toList parentVector)))
  traverse_ validateBinderIntro (zip [0 ..] (V.toList binderIntroVector))
  preorder <- buildPreorderIndex parentVector
  liftVector <- buildLift parentVector
  pure
    ScopeIndex
      { siParent = parentVector,
        siDepth = preorderDepth preorder,
        siSubtreeEnd = preorderSubtreeEnd preorder,
        siLift = liftVector,
        siHasBranch = preorderHasBranch preorder,
        siDeepest = preorderDeepest preorder,
        siRoot = rootScopeId,
        siBinderIntro = V.map ScopeId binderIntroVector
      }
  where
    validateParentEdge (scopeKey, parentKey) =
      when (parentKey < 0 || parentKey >= scopeKey) $
        Left (ScopeParentEdgeInvalid (ScopeId scopeKey) parentKey)

    validateBinderIntro (binderKey, introScopeKey) =
      when (introScopeKey < 0 || introScopeKey >= V.length parentVector) $
        Left (ScopeBinderIntroInvalid (BinderId binderKey) introScopeKey)

scopeIndexRoot :: ScopeIndex -> ScopeId
scopeIndexRoot =
  siRoot

scopeParentId :: ScopeIndex -> ScopeId -> Either ScopeLookupFailure ScopeId
scopeParentId scopeIndex scopeId =
  ScopeId <$> scopeVectorValue ScopeIdOutsideIndex scopeId (siParent scopeIndex)

scopeDepthOf :: ScopeIndex -> ScopeId -> Either ScopeLookupFailure Int
scopeDepthOf scopeIndex scopeId =
  scopeVectorValue ScopeIdOutsideIndex scopeId (siDepth scopeIndex)

scopeIsAncestorOf :: ScopeIndex -> ScopeId -> ScopeId -> Either ScopeLookupFailure Bool
scopeIsAncestorOf scopeIndex leftScope rightScope = do
  leftEnd <- scopeVectorValue ScopeIdOutsideIndex leftScope (siSubtreeEnd scopeIndex)
  _ <- scopeVectorValue ScopeIdOutsideIndex rightScope (siSubtreeEnd scopeIndex)
  let leftKey = scopeIdKey leftScope
      rightKey = scopeIdKey rightScope
  pure (leftKey <= rightKey && rightKey < leftEnd)

scopeComparable :: ScopeIndex -> ScopeId -> ScopeId -> Either ScopeLookupFailure Bool
scopeComparable scopeIndex leftScope rightScope =
  (||)
    <$> scopeIsAncestorOf scopeIndex leftScope rightScope
    <*> scopeIsAncestorOf scopeIndex rightScope leftScope

scopeLca :: ScopeIndex -> ScopeId -> ScopeId -> Either ScopeLookupFailure ScopeId
scopeLca scopeIndex leftScope rightScope = do
  leftAncestor <- scopeIsAncestorOf scopeIndex leftScope rightScope
  rightAncestor <- scopeIsAncestorOf scopeIndex rightScope leftScope
  if leftAncestor
    then pure leftScope
    else
      if rightAncestor
        then pure rightScope
        else scopeParentId scopeIndex =<< climb leftScope (V.length (siLift scopeIndex) - 1)
  where
    climb currentScope liftIndex
      | liftIndex < 0 =
          pure currentScope
      | otherwise = do
          ancestorScope <- liftAncestor scopeIndex liftIndex currentScope
          ancestorOfRight <- scopeIsAncestorOf scopeIndex ancestorScope rightScope
          if ancestorOfRight
            then climb currentScope (liftIndex - 1)
            else climb ancestorScope (liftIndex - 1)

scopeCtxLeq :: ScopeIndex -> ScopeCtx -> ScopeCtx -> Either ScopeLookupFailure Bool
scopeCtxLeq _ IncompatibleScope IncompatibleScope =
  Right True
scopeCtxLeq _ IncompatibleScope _ =
  Right False
scopeCtxLeq _ _ IncompatibleScope =
  Right True
scopeCtxLeq scopeIndex (ActualScope leftScope) (ActualScope rightScope) =
  scopeIsAncestorOf scopeIndex leftScope rightScope

scopeCtxMeet :: ScopeIndex -> ScopeCtx -> ScopeCtx -> Either ScopeLookupFailure ScopeCtx
scopeCtxMeet _ IncompatibleScope rightCtx =
  Right rightCtx
scopeCtxMeet _ leftCtx IncompatibleScope =
  Right leftCtx
scopeCtxMeet scopeIndex (ActualScope leftScope) (ActualScope rightScope) =
  ActualScope <$> scopeLca scopeIndex leftScope rightScope

scopeCtxJoin :: ScopeIndex -> ScopeCtx -> ScopeCtx -> Either ScopeLookupFailure ScopeCtx
scopeCtxJoin _ IncompatibleScope _ =
  Right IncompatibleScope
scopeCtxJoin _ _ IncompatibleScope =
  Right IncompatibleScope
scopeCtxJoin scopeIndex (ActualScope leftScope) (ActualScope rightScope) = do
  leftAncestor <- scopeIsAncestorOf scopeIndex leftScope rightScope
  rightAncestor <- scopeIsAncestorOf scopeIndex rightScope leftScope
  pure $
    if leftAncestor
      then ActualScope rightScope
      else
        if rightAncestor
          then ActualScope leftScope
          else IncompatibleScope

scopeObservedCount :: ScopeIndex -> Int
scopeObservedCount =
  V.length . siParent

scopeObservedContexts :: ScopeIndex -> Either ScopeLookupFailure [ScopeCtx]
scopeObservedContexts scopeIndex =
  Right
    ( fmap (ActualScope . ScopeId) [0 .. V.length (siParent scopeIndex) - 1]
        <> [IncompatibleScope | siHasBranch scopeIndex]
    )

scopeTopCtx :: ScopeIndex -> Either ScopeLookupFailure ScopeCtx
scopeTopCtx scopeIndex =
  Right
    ( if siHasBranch scopeIndex
        then IncompatibleScope
        else ActualScope (siDeepest scopeIndex)
    )

scopeBottomCtx :: ScopeIndex -> ScopeCtx
scopeBottomCtx =
  ActualScope . siRoot

binderIntroScope :: ScopeIndex -> BinderId -> Either ScopeLookupFailure ScopeId
binderIntroScope scopeIndex binderId =
  binderVectorValue binderId (siBinderIntro scopeIndex)

binderSiteScope :: ScopeIndex -> BinderId -> Either ScopeLookupFailure ScopeId
binderSiteScope scopeIndex binderId =
  scopeParentId scopeIndex =<< binderIntroScope scopeIndex binderId

emptyFreeScopeSummary :: FreeScopeSummary
emptyFreeScopeSummary =
  FreeScopeSummary (smallArrayFromList [])

singletonFreeScopeSummary :: ScopeId -> FreeScopeSummary
singletonFreeScopeSummary scopeId =
  FreeScopeSummary (smallArrayFromList [scopeId])

mergeFreeScopeSummary :: ScopeIndex -> FreeScopeSummary -> FreeScopeSummary -> Either ScopeLookupFailure FreeScopeSummary
mergeFreeScopeSummary scopeIndex =
  mergeFreeScopeSummaryByEither (scopeDepthOf scopeIndex)

mergeFreeScopeSummaryBy :: (ScopeId -> Int) -> FreeScopeSummary -> FreeScopeSummary -> FreeScopeSummary
mergeFreeScopeSummaryBy depthOf leftSummary rightSummary =
  either absurd id (mergeFreeScopeSummaryByEither (Right . depthOf) leftSummary rightSummary)

mergeFreeScopeSummaryByEither ::
  (ScopeId -> Either failure Int) ->
  FreeScopeSummary ->
  FreeScopeSummary ->
  Either failure FreeScopeSummary
mergeFreeScopeSummaryByEither depthOf leftSummary rightSummary =
  FreeScopeSummary . smallArrayFromList
    <$> go (freeScopeSummaryToList leftSummary) (freeScopeSummaryToList rightSummary)
  where
    go leftValues rightValues =
      case (leftValues, rightValues) of
        ([], []) ->
          Right []
        ([], _) ->
          Right rightValues
        (_, []) ->
          Right leftValues
        (leftScope : remainingLeft, rightScope : remainingRight)
          | leftScope == rightScope ->
              (leftScope :) <$> go remainingLeft remainingRight
          | otherwise -> do
              leftDepth <- depthOf leftScope
              rightDepth <- depthOf rightScope
              case compare leftDepth rightDepth of
                GT ->
                  (leftScope :) <$> go remainingLeft rightValues
                LT ->
                  (rightScope :) <$> go leftValues remainingRight
                EQ ->
                  case compare leftScope rightScope of
                    LT ->
                      (leftScope :) <$> go remainingLeft rightValues
                    GT ->
                      (rightScope :) <$> go leftValues remainingRight
                    EQ ->
                      (leftScope :) <$> go remainingLeft remainingRight

deleteFreeScopeSummary :: ScopeId -> FreeScopeSummary -> FreeScopeSummary
deleteFreeScopeSummary targetScope summaryValue =
  FreeScopeSummary
    ( smallArrayFromList
        (filter (/= targetScope) (freeScopeSummaryToList summaryValue))
    )

freeScopeSummaryContains :: ScopeId -> FreeScopeSummary -> Bool
freeScopeSummaryContains targetScope summaryValue =
  go 0
  where
    FreeScopeSummary scopeArray = summaryValue
    go indexValue
      | indexValue >= sizeofSmallArray scopeArray =
          False
      | indexSmallArray scopeArray indexValue == targetScope =
          True
      | otherwise =
          go (indexValue + 1)

freeScopeSummarySize :: FreeScopeSummary -> Int
freeScopeSummarySize (FreeScopeSummary scopeArray) =
  sizeofSmallArray scopeArray

freeScopeSummaryToList :: FreeScopeSummary -> [ScopeId]
freeScopeSummaryToList (FreeScopeSummary scopeArray) =
  scopeArrayToList scopeArray

freeScopeSupportAnchor :: ScopeIndex -> FreeScopeSummary -> ScopeId
freeScopeSupportAnchor scopeIndex (FreeScopeSummary scopeArray)
  | sizeofSmallArray scopeArray > 0 =
      indexSmallArray scopeArray 0
  | otherwise =
      siRoot scopeIndex

type PreorderIndex :: Type
data PreorderIndex = PreorderIndex
  { preorderDepth :: !(Vector Int),
    preorderSubtreeEnd :: !(Vector Int),
    preorderHasBranch :: !Bool,
    preorderDeepest :: !ScopeId
  }

type OpenScope :: Type
data OpenScope = OpenScope
  { openScopeKey :: !Int,
    openScopeDepth :: !Int
  }

type PreorderBuild :: Type
data PreorderBuild = PreorderBuild
  { pbOpenScopes :: ![OpenScope],
    pbDepthsRev :: ![Int],
    pbSubtreeEnds :: !(IntMap.IntMap Int),
    pbChildCounts :: !(IntMap.IntMap Int),
    pbHasBranch :: !Bool,
    pbDeepest :: !(ScopeId, Int)
  }

buildPreorderIndex :: Vector Int -> Either ScopeIndexFailure PreorderIndex
buildPreorderIndex parentVector = do
  finalBuild <-
    foldM
      appendPreorderScope
      PreorderBuild
        { pbOpenScopes = [OpenScope 0 0],
          pbDepthsRev = [0],
          pbSubtreeEnds = IntMap.empty,
          pbChildCounts = IntMap.singleton 0 0,
          pbHasBranch = False,
          pbDeepest = (rootScopeId, 0)
        }
      (zip [1 ..] (drop 1 (V.toList parentVector)))
  let scopeCount = V.length parentVector
      subtreeEnds =
        foldr
          (\openScope -> IntMap.insert (openScopeKey openScope) scopeCount)
          (pbSubtreeEnds finalBuild)
          (pbOpenScopes finalBuild)
  materializedEnds <-
    V.fromList
      <$> traverse
        ( \scopeKey ->
            maybe
              (Left (ScopeSubtreeEndMissing (ScopeId scopeKey)))
              Right
              (IntMap.lookup scopeKey subtreeEnds)
        )
        [0 .. scopeCount - 1]
  pure
    PreorderIndex
      { preorderDepth = V.fromList (reverse (pbDepthsRev finalBuild)),
        preorderSubtreeEnd = materializedEnds,
        preorderHasBranch = pbHasBranch finalBuild,
        preorderDeepest = fst (pbDeepest finalBuild)
      }

appendPreorderScope :: PreorderBuild -> (Int, Int) -> Either ScopeIndexFailure PreorderBuild
appendPreorderScope buildState (scopeKey, parentKey) = do
  (closedScopes, parentDepth, remainingOpen) <-
    closeScopesUntil parentKey (pbOpenScopes buildState)
  let childCount = IntMap.findWithDefault 0 parentKey (pbChildCounts buildState) + 1
      scopeDepth = parentDepth + 1
      deepestValue =
        if scopeDepth > snd (pbDeepest buildState)
          then (ScopeId scopeKey, scopeDepth)
          else pbDeepest buildState
  pure
    buildState
      { pbOpenScopes = OpenScope scopeKey scopeDepth : remainingOpen,
        pbDepthsRev = scopeDepth : pbDepthsRev buildState,
        pbSubtreeEnds =
          foldr
            (`IntMap.insert` scopeKey)
            (pbSubtreeEnds buildState)
            closedScopes,
        pbChildCounts =
          IntMap.insert scopeKey 0
            (IntMap.insert parentKey childCount (pbChildCounts buildState)),
        pbHasBranch = pbHasBranch buildState || childCount > 1,
        pbDeepest = deepestValue
      }
  where
    closeScopesUntil targetScope openScopes =
      case break ((== targetScope) . openScopeKey) openScopes of
        (_, []) ->
          Left (ScopeParentNotPreorder (ScopeId scopeKey) parentKey)
        (closedScopes, parentScope : survivingScopes) ->
          Right
            ( fmap openScopeKey closedScopes,
              openScopeDepth parentScope,
              parentScope : survivingScopes
            )

buildLift :: Vector Int -> Either ScopeIndexFailure (Vector (Vector Int))
buildLift parentVector =
  V.fromList . reverse
    <$> foldM appendLevel [parentVector] [1 .. levelCount - 1]
  where
    scopeCount = V.length parentVector

    levelCount =
      max 1 (length (takeWhile (< scopeCount) (iterate (* 2) 1)))

    appendLevel :: [Vector Int] -> Int -> Either ScopeIndexFailure [Vector Int]
    appendLevel levels _ =
      case levels of
        [] ->
          Left (ScopeLiftMissing rootScopeId)
        previousLevel : _ -> do
          nextLevel <- traverse (nextAncestor previousLevel) previousLevel
          Right (nextLevel : levels)

    nextAncestor :: Vector Int -> Int -> Either ScopeIndexFailure Int
    nextAncestor previousLevel ancestorKey =
      maybe
        (Left (ScopeLiftMissing (ScopeId ancestorKey)))
        Right
        (previousLevel V.!? ancestorKey)

liftAncestor :: ScopeIndex -> Int -> ScopeId -> Either ScopeLookupFailure ScopeId
liftAncestor scopeIndex liftIndex scopeId =
  case siLift scopeIndex V.!? liftIndex of
    Nothing ->
      Left (ScopeLiftLevelOutsideIndex liftIndex (V.length (siLift scopeIndex)))
    Just liftLevel ->
      ScopeId <$> scopeVectorValue ScopeIdOutsideIndex scopeId liftLevel

scopeVectorValue :: (ScopeId -> Int -> ScopeLookupFailure) -> ScopeId -> Vector value -> Either ScopeLookupFailure value
scopeVectorValue failure scopeId vectorValue =
  maybe
    (Left (failure scopeId (V.length vectorValue)))
    Right
    (vectorValue V.!? scopeIdKey scopeId)

binderVectorValue :: BinderId -> Vector ScopeId -> Either ScopeLookupFailure ScopeId
binderVectorValue binderId vectorValue =
  maybe
    (Left (BinderIdOutsideIndex binderId (V.length vectorValue)))
    Right
    (vectorValue V.!? binderIdKey binderId)