packages feed

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

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}

module Moonlight.Pale.Ghc.Expr.Convert.State
  ( BinderOwner (..),
    GroupMachinery (..),
    emptyGroupMachinery,
    ConvState (..),
    ConvM,
    throwConvert,
    liftConvertEither,
    runInstanceMethodSection,
    initialConvState,
    initialConvStateWithRecordFieldEnvironment,
    resolveRecordWildcardFields,
    resolveVarRef,
    currentScopeId,
    withScope,
    freshChildScope,
    scopeDepthInState,
    mkConvExpr,
    throwUnsupportedExpression,
    freshBinderAnn,
    recordLambdaSite,
    recordLetSite,
    currentScopeAlgebra,
    modifyGroupMachinery,
    freshBindingGroupId,
    registerBindingOwners,
    withActiveBindingRow,
    recordBindingDependency,
    takeBindingDependencies
  )
where

import Control.Monad.State.Strict (StateT (..), gets, modify', runStateT, state)
import Control.Monad.Trans.Class (lift)
import Data.Kind (Type)
import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NonEmpty
import Data.Map.Strict qualified as Map
import Data.Set (Set)
import Data.Set qualified as Set
import GHC.Types.Name.Reader (RdrName)
import Moonlight.Core (BinderId (..), binderIdKey)
import Moonlight.Pale.Ghc.Expr.Convert.FreeScopes (ScopeAlgebra (..))
import Moonlight.Pale.Ghc.Expr.Convert.FreeScopes qualified as FreeScopes
import Moonlight.Pale.Ghc.Expr.Convert.Obstruction
import Moonlight.Pale.Ghc.Expr.Convert.Row
import Moonlight.Pale.Ghc.Expr.Opaque
import Moonlight.Pale.Ghc.Expr.Scope
import Moonlight.Pale.Ghc.Expr.Syntax

type BinderOwner :: Type
newtype BinderOwner = BinderOwner
  { binderOwnerGroup :: BindingGroupId
  }
  deriving stock (Eq, Ord, Show)

type GroupMachinery :: Type
data GroupMachinery = GroupMachinery
  { gmNextGroupId :: !Int,
    gmBinderOwners :: !(IntMap BinderOwner),
    gmActiveRows :: !(IntMap Int),
    gmDependencies :: !(IntMap (IntMap (Set BinderId)))
  }

emptyGroupMachinery :: GroupMachinery
emptyGroupMachinery =
  GroupMachinery 0 IntMap.empty IntMap.empty IntMap.empty

type ConvState :: Type
data ConvState = ConvState
  { csRecordFieldEnvironment :: !RecordFieldEnvironment,
    csNextBinderId :: !Int,
    csNextScopeId :: !Int,
    csCurrentScope :: !ScopeId,
    csScopeParentsRev :: ![Int],
    csScopeDepths :: !(IntMap Int),
    csBinderIntroRev :: ![Int],
    csBinderIntroMap :: !(IntMap ScopeId),
    csGroupMachinery :: !GroupMachinery,
    csLambdaSites :: ![BinderAnn],
    csLetSites :: ![BinderAnn]
  }

type ConvM :: Type -> Type
type ConvM = StateT ConvState (Either ConvertObstruction)

throwConvert :: ConvertObstruction -> ConvM value
throwConvert =
  lift . Left

liftConvertEither :: Either ConvertObstruction value -> ConvM value
liftConvertEither =
  either throwConvert pure

runInstanceMethodSection ::
  Maybe SourceRegion ->
  ConvM value ->
  ConvM (Either InstanceMethodObstruction value)
runInstanceMethodSection methodRegion action =
  StateT
    ( \checkpointState ->
        case runStateT action checkpointState of
          Right (value, methodState) ->
            Right (Right value, methodState)
          Left obstruction ->
            case recoverableInstanceMethodObstruction methodRegion obstruction of
              Just methodObstruction ->
                Right (Left methodObstruction, checkpointState)
              Nothing ->
                Left obstruction
    )

initialConvState :: ConvState
initialConvState =
  initialConvStateWithRecordFieldEnvironment emptyRecordFieldEnvironment

initialConvStateWithRecordFieldEnvironment ::
  RecordFieldEnvironment ->
  ConvState
initialConvStateWithRecordFieldEnvironment recordFieldEnvironment =
  ConvState
    { csRecordFieldEnvironment = recordFieldEnvironment,
      csNextBinderId = 0,
      csNextScopeId = 1,
      csCurrentScope = rootScopeId,
      csScopeParentsRev = [0],
      csScopeDepths = IntMap.singleton 0 0,
      csBinderIntroRev = [],
      csBinderIntroMap = IntMap.empty,
      csGroupMachinery = emptyGroupMachinery,
      csLambdaSites = [],
      csLetSites = []
    }

resolveRecordWildcardFields ::
  SourceRegion ->
  RdrName ->
  ConvM [RdrName]
resolveRecordWildcardFields wildcardRegion constructorName = do
  recordFieldEnvironment <- gets csRecordFieldEnvironment
  either
    (throwConvert . ConvertRecordWildcardResolutionUnavailable wildcardRegion)
    pure
    (resolveRecordFieldEnvironment recordFieldEnvironment constructorName)

resolveVarRef :: Env -> RdrName -> ConvM HsVarRef
resolveVarRef env nameValue =
  case Map.lookup nameValue env of
    Nothing ->
      pure (GlobalName nameValue)
    Just binderAnn -> do
      recordBindingDependency binderAnn
      pure (LocalName binderAnn)

currentScopeId :: ConvM ScopeId
currentScopeId =
  gets csCurrentScope

withScope :: ScopeId -> ConvM value -> ConvM value
withScope scopeId action = do
  previousScope <- gets csCurrentScope
  modify' (\stateValue -> stateValue {csCurrentScope = scopeId})
  resultValue <- action
  modify' (\stateValue -> stateValue {csCurrentScope = previousScope})
  pure resultValue

freshChildScope :: ConvM ScopeId
freshChildScope = do
  parentScope <- gets csCurrentScope
  parentDepth <- scopeDepthInState parentScope
  nextScopeId <- gets csNextScopeId
  childScope <-
    either
      (throwConvert . ConvertFreshScopeIdFailure nextScopeId)
      pure
      (mkScopeId nextScopeId)
  let !parentKey = scopeIdKey parentScope
  modify'
    ( \stateValue ->
        stateValue
          { csNextScopeId = nextScopeId + 1,
            csScopeParentsRev = parentKey : csScopeParentsRev stateValue,
            csScopeDepths = IntMap.insert nextScopeId (parentDepth + 1) (csScopeDepths stateValue)
          }
    )
  pure childScope

scopeDepthInState :: ScopeId -> ConvM Int
scopeDepthInState scopeId = do
  depthMap <- gets csScopeDepths
  maybe
    (throwConvert (ConvertMissingScopeDepth scopeId))
    pure
    (IntMap.lookup (scopeIdKey scopeId) depthMap)

mkConvExpr :: Maybe SourceRegion -> HsExprF Expr -> ConvM Expr
mkConvExpr region nodeValue = do
  occurrenceScope <- currentScopeId
  scopeAlgebra <- currentScopeAlgebra
  freeScopes <- liftConvertEither (FreeScopes.freeScopesExpr scopeAlgebra nodeValue)
  pure
    Expr
      { exprRegion = region,
        exprScope = occurrenceScope,
        exprFreeScopes = freeScopes,
        exprNode = nodeValue
      }

throwUnsupportedExpression :: Maybe SourceRegion -> HsOpaqueTag -> ConvM value
throwUnsupportedExpression region opaqueTag =
  throwConvert (ConvertUnsupportedExpression region opaqueTag)

freshBinderAnn :: RdrName -> ConvM BinderAnn
freshBinderAnn binderName = do
  nextBinderId <- gets csNextBinderId
  introScope <- gets csCurrentScope
  let !introKey = scopeIdKey introScope
  modify'
    ( \stateValue ->
        stateValue
          { csNextBinderId = nextBinderId + 1,
            csBinderIntroRev = introKey : csBinderIntroRev stateValue,
            csBinderIntroMap = IntMap.insert nextBinderId introScope (csBinderIntroMap stateValue)
          }
    )
  pure
    BinderAnn
      { baId = BinderId nextBinderId,
        baName = binderName
      }

recordLambdaSite :: BinderAnn -> ConvM ()
recordLambdaSite binderAnn =
  modify' (\stateValue -> stateValue {csLambdaSites = binderAnn : csLambdaSites stateValue})

recordLetSite :: BinderAnn -> ConvM ()
recordLetSite binderAnn =
  modify' (\stateValue -> stateValue {csLetSites = binderAnn : csLetSites stateValue})

currentScopeAlgebra :: ConvM (ScopeAlgebra ConvertObstruction)
currentScopeAlgebra = do
  depthMap <- gets csScopeDepths
  introMap <- gets csBinderIntroMap
  pure
    ScopeAlgebra
      { saScopeDepth =
          \scopeId ->
            maybe
              (Left (ConvertMissingScopeSummaryDepth scopeId))
              Right
              (IntMap.lookup (scopeIdKey scopeId) depthMap),
        saBinderIntro =
          \binderAnn ->
            maybe
              (Left (ConvertMissingBinderIntro (baId binderAnn)))
              Right
              (IntMap.lookup (binderIdKey (baId binderAnn)) introMap)
      }

modifyGroupMachinery :: (GroupMachinery -> GroupMachinery) -> ConvM ()
modifyGroupMachinery adjustMachinery =
  modify'
    ( \stateValue ->
        stateValue {csGroupMachinery = adjustMachinery (csGroupMachinery stateValue)}
    )

freshBindingGroupId :: ConvM BindingGroupId
freshBindingGroupId = do
  nextGroupId <- gets (gmNextGroupId . csGroupMachinery)
  modifyGroupMachinery
    (\machineryValue -> machineryValue {gmNextGroupId = nextGroupId + 1})
  pure (BindingGroupId nextGroupId)

registerBindingOwners :: BindingGroupId -> NonEmpty HsPatF -> ConvM ()
registerBindingOwners bindingGroupId bindingPatterns =
  modifyGroupMachinery
    ( \machineryValue ->
        machineryValue
          { gmBinderOwners =
              foldr
                (uncurry IntMap.insert)
                (gmBinderOwners machineryValue)
                [ ( binderIdKey (baId binderAnn),
                    BinderOwner bindingGroupId
                  )
                | bindingPatternValue <- NonEmpty.toList bindingPatterns,
                  binderAnn <- patBinders bindingPatternValue
                ]
          }
    )

withActiveBindingRow :: BindingGroupId -> Int -> ConvM value -> ConvM value
withActiveBindingRow bindingGroupId rowIndex action = do
  let groupKey = bindingGroupIdKey bindingGroupId
  previousRow <- gets (IntMap.lookup groupKey . gmActiveRows . csGroupMachinery)
  modifyGroupMachinery
    ( \machineryValue ->
        machineryValue
          { gmActiveRows =
              IntMap.insert groupKey rowIndex (gmActiveRows machineryValue)
          }
    )
  resultValue <- action
  modifyGroupMachinery
    ( \machineryValue ->
        machineryValue
          { gmActiveRows =
              maybe
                (IntMap.delete groupKey (gmActiveRows machineryValue))
                (\previousRowIndex -> IntMap.insert groupKey previousRowIndex (gmActiveRows machineryValue))
                previousRow
          }
    )
  pure resultValue

recordBindingDependency :: BinderAnn -> ConvM ()
recordBindingDependency binderAnn = do
  maybeOwner <-
    gets
      (IntMap.lookup (binderIdKey (baId binderAnn)) . gmBinderOwners . csGroupMachinery)
  case maybeOwner of
    Nothing ->
      pure ()
    Just binderOwner -> do
      let groupKey = bindingGroupIdKey (binderOwnerGroup binderOwner)
      maybeSourceRow <-
        gets (IntMap.lookup groupKey . gmActiveRows . csGroupMachinery)
      case maybeSourceRow of
        Nothing ->
          pure ()
        Just sourceRow ->
          modifyGroupMachinery
            ( \machineryValue ->
                machineryValue
                  { gmDependencies =
                      IntMap.insertWith
                        (IntMap.unionWith Set.union)
                        groupKey
                        (IntMap.singleton sourceRow (Set.singleton (baId binderAnn)))
                        (gmDependencies machineryValue)
                  }
            )

takeBindingDependencies ::
  BindingGroupId ->
  [BinderAnn] ->
  ConvM (IntMap (Set BinderId))
takeBindingDependencies bindingGroupId bindingAnnotations = do
  let groupKey = bindingGroupIdKey bindingGroupId
  state
    ( \stateValue ->
        let machineryValue = csGroupMachinery stateValue
         in ( IntMap.findWithDefault
                IntMap.empty
                groupKey
                (gmDependencies machineryValue),
              stateValue
                { csGroupMachinery =
                    machineryValue
                      { gmBinderOwners =
                          foldr
                            ( \binderAnn binderOwners ->
                                IntMap.delete
                                  (binderIdKey (baId binderAnn))
                                  binderOwners
                            )
                            (gmBinderOwners machineryValue)
                            bindingAnnotations,
                        gmDependencies =
                          IntMap.delete groupKey (gmDependencies machineryValue)
                      }
                }
            )
    )