packages feed

indigo-0.4: src/Indigo/Compilation/Sequential.hs

-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

-- | 'Instruction' datatype and its compilations.
--
-- The idea behind this module is to provide an intermediate representation that
-- can:
--
-- - be generated from the frontend freer monad
-- - be compiled to the backend 'IndigoState'
-- - be easy to analyze, manipulate and modify
--
-- This is meant to be the common ground for modular optimizations on the code
-- before this is handed off to the backend and eventually translated to
-- Michelson.

module Indigo.Compilation.Sequential
  ( Block
  , Instruction (..)

  , IndigoSeqCaseClause (..)
  , CaseBranch (..)

  -- * Translations
  , SequentialHooks (..)
  , stmtHookL
  , InstrCollector (..)
  , indigoMtoSequential
  , sequentialToLorentz

  -- * Case machinery
  , updateClauses
  , mapMClauses
  ) where

import Prelude

import Data.Vinyl.Core (RMap(..))

import Lens.Micro.TH (makeLensesFor)
import Lorentz.Entrypoints.Helpers (RequireSumType)
import qualified Lorentz.Run as L (Contract)
import Michelson.Typed.Haskell.Instr.Sum (CaseClauseParam(..), CtorField(..))
import Util.TypeLits (AppendSymbol)

import Indigo.Backend
import Indigo.Frontend.Program
import qualified Indigo.Frontend.Statement as S
import Indigo.Internal (Expr, HasField)
import Indigo.Internal.Object (IsObject)
import Indigo.Internal.SIS
import Indigo.Internal.State hiding ((>>))
import qualified Indigo.Internal.State as St
import Indigo.Internal.Var
import Indigo.Lorentz
import qualified Michelson.Typed as MT

-- | Simple synonym for a list of 'Instruction'
type Block = [Instruction]

-- | Data type representing an instruction.
--
-- Differently from the frontend this is not used to build a Monad of some kind,
-- it is instead based on having as argument the variable to associate with the
-- resulting value (if any).
--
-- This is combined in simple lists, named 'Block', and it is intended to be
-- easily altered, this is because these are used as the intermediate representation
-- between the frontend and the backend, where optimizations can occur.
data Instruction where
  LiftIndigoState :: (forall inp. SomeIndigoState inp) -> Instruction
  Comment :: Text -> Instruction

  AssignVar :: KnownValue x => Var x -> Expr x -> Instruction
  SetVar :: KnownValue x => Var x -> Expr x -> Instruction
  VarModification
    :: (IsObject x, KnownValue y)
    => [y, x] :-> '[x]
    -> Var x
    -> Expr y
    -> Instruction
  SetField
    :: ( HasField store fname ftype
       , IsObject store
       , IsObject ftype
       )
    => Var store -> Label fname -> Expr ftype -> Instruction

  LambdaCall1
    :: LambdaKind st arg ret extra
    -- ^ Kind of lambda (pure, storage modification, fully functional lambda with effects)
    -> String
    -- ^ Name of the lambda
    -> Expr arg
    -- ^ Expression for the lambda argument
    -> Var arg
    -- ^ Variable for the argument value (available to the lambda code block)
    -> Block
    -- ^ Code block for the lambda
    -> ret
    -- ^ Return value(s) of the lambda
    -> RetVars ret
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction

  CreateLambda1
    :: CreateLambda1CGeneric extra arg ret
    => StackVars (arg : extra)
    -- ^ Initial 'StackVars' to be used in the lambda code
    -> Var arg
    -- ^ Variable for the argument value (available to the lambda code block)
    -> Block
    -- ^ Code block for the lambda
    -> ret
    -- ^ Return value(s) of the lambda
    -> Var (Lambda1Generic extra arg ret)
    -- ^ Variable that will be assigned to the resulting lambda
    -> Instruction

  ExecLambda1
    :: LambdaKind st arg ret extra
    -> Proxy ret
    -> Expr arg
    -- ^ Expression for the lambda argument
    -> Var (Lambda1Generic extra arg ret)
    -- ^ Variable of the lambda to be executed
    -> RetVars ret
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction

  Scope
    :: ScopeCodeGen ret
    => Block
    -- ^ Code block to execute inside the scope
    -> ret
    -- ^ Return value(s) of the scoped code block
    -> RetVars ret
    -- ^ Variable that will be assigned to the resulting value(s)
    -> Instruction
  If
    :: IfConstraint a b
    => Expr Bool
    -- ^ Expression for the control flow
    -> Block
    -- ^ Code block for the positive branch
    -> a
    -- ^ Return value(s) of the positive branch
    -> Block
    -- ^ Code block for the negative branch
    -> b
    -- ^ Return value(s) of the negative branch
    -> RetVars a
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction
  IfSome
    :: (IfConstraint a b, KnownValue x)
    => Expr (Maybe x)
    -- ^ Expression for the control flow
    -> Var x
    -- ^ Variable for the 'Just' value (available to the next code block)
    -> Block
    -- ^ Code block for the 'Just' branch
    -> a
    -- ^ Return value(s) of the 'Just' branch
    -> Block
    -- ^ Code block for the 'Nothing' branch
    -> b
    -- ^ Return value(s) of the 'Nothing' branch
    -> RetVars a
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction
  IfRight
    :: (IfConstraint a b, KnownValue r, KnownValue l)
    => Expr (Either l r)
    -- ^ Expression for the control flow
    -> Var r
    -- ^ Variable for the 'Right' value (available to the next code block)
    -> Block
    -- ^ Code block for the 'Right' branch
    -> a
    -- ^ Return value(s) of the 'Right' branch
    -> Var l
    -- ^ Variable for the 'Left' value (available to the next code block)
    -> Block
    -- ^ Code block for the 'Left' branch
    -> b
    -- ^ Return value(s) of the 'Left' branch
    -> RetVars a
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction
  IfCons
    :: (IfConstraint a b, KnownValue x)
    => Expr (List x)
    -- ^ Expression for the control flow
    -> Var x
    -- ^ Variable for the "head" value (available to the next code block)
    -> Var (List x)
    -- ^ Variable for the "tail" value (available to the next code block)
    -> Block
    -- ^ Code block for the non-empty list branch
    -> a
    -- ^ Return value(s) of the non-empty list branch
    -> Block
    -- ^ Code block for the empty list branch
    -> b
    -- ^ Return value(s) of the empty list branch
    -> RetVars a
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction

  Case
    :: CaseCommon dt ret clauses
    => Expr dt
    -> clauses
    -> RetVars ret
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction
  EntryCase
    :: ( CaseCommon dt ret clauses
       , DocumentEntrypoints entryPointKind dt
       )
    => Proxy entryPointKind
    -> Expr dt
    -> clauses
    -> RetVars ret
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction
  EntryCaseSimple
    :: ( CaseCommon dt ret clauses
       , DocumentEntrypoints PlainEntrypointsKind dt
       , NiceParameterFull dt
       , RequireFlatParamEps dt
       )
    => Expr dt
    -> clauses
    -> RetVars ret
    -- ^ Variable(s) that will be assigned to the resulting value(s)
    -> Instruction

  While
    :: Expr Bool
    -- ^ Expression for the control flow
    -> Block
    -- ^ Block of code to execute, as long as the expression holds 'True'
    -> Instruction
  WhileLeft
    :: (KnownValue l, KnownValue r)
    => Expr (Either l r)
    -- ^ Expression for the control flow value
    -> Var l
    -- ^ Variable for the 'Left' value (available to the code block)
    -> Block
    -- ^ Code block to execute while the value is 'Left'
    -> Var r
    -- ^ Variable that will be assigned to the resulting value
    -> Instruction
  ForEach
    :: (IterOpHs a, KnownValue (IterOpElHs a))
    => Expr a
    -- ^ Expression for the container to traverse
    -> Var (IterOpElHs a)
    -- ^ Variable for the current item (available to the code block)
    -> Block
    -- ^ Code block to execute over each element of the container
    -> Instruction

  ContractName
    :: Text
    -> Block
    -> Instruction
  DocGroup
    :: DocGrouping
    -> Block
    -> Instruction
  ContractGeneral
    :: Block
    -> Instruction
  FinalizeParamCallingDoc
    :: (NiceParameterFull cp, RequireSumType cp)
    => Var cp
    -> Block
    -> Expr cp
    -> Instruction

  TransferTokens
    :: (NiceParameter p, HasSideEffects)
    => Expr p
    -> Expr Mutez
    -> Expr (ContractRef p)
    -> Instruction
  SetDelegate
    :: HasSideEffects
    => Expr (Maybe KeyHash)
    -> Instruction

  CreateContract
    :: (HasSideEffects, NiceStorage s, NiceParameterFull p)
    => L.Contract p s
    -> Expr (Maybe KeyHash)
    -> Expr Mutez
    -> Expr s
    -> Var Address
    -- ^ Variable that will be assigned to the resulting 'Address'
    -> Instruction
  SelfCalling
    :: ( NiceParameterFull p
       , KnownValue (GetEntrypointArgCustom p mname)
       )
    => Proxy p
    -> EntrypointRef mname
    -> Var (ContractRef (GetEntrypointArgCustom p mname))
    -- ^ Variable that will be assigned to the resulting 'ContractRef'
    -> Instruction
  ContractCalling
    :: ( HasEntrypointArg cp epRef epArg
       , ToTAddress cp addr
       , ToT addr ~ ToT Address
       , KnownValue epArg
       )
    => Proxy cp
    -> epRef
    -> Expr addr
    -> Var (Maybe (ContractRef epArg))
    -- ^ Variable that will be assigned to the resulting 'ContractRef'
    -> Instruction

  Fail
    :: (forall inp. SomeIndigoState inp)
    -> Instruction
  FailOver
    :: (forall inp. Expr a -> SomeIndigoState inp)
    -> Expr a
    -> Instruction

----------------------------------------------------------------------------
-- Translations
----------------------------------------------------------------------------

-- | Data type internally used to collect 'Instruction's from 'IndigoM'
data InstrCollector = InstrCollector
  { nextRef   :: RefId
  , instrList :: Block
  , seqHooks  :: SequentialHooks
  }

newtype SequentialHooks = SequentialHooks {
    shStmtHook    :: CallStack -> Block -> State InstrCollector ()
  }

instance Semigroup SequentialHooks where
  SequentialHooks s <> SequentialHooks s1 = SequentialHooks (\t -> s t >> s1 t)

instance Monoid SequentialHooks where
  mempty = SequentialHooks (const $ appendNewInstrs . reverse)

-- | Transformation from 'IndigoM' to a 'Block' of 'Instruction's.
--
-- Requires the first non-used 'RefId' and returns the next one.
indigoMtoSequential
  :: RefId
  -> SequentialHooks
  -> IndigoM a
  -> (Block, RefId)
indigoMtoSequential refId hook code =
  let InstrCollector {..} = snd $ instrCollect refId hook code
  in (instrList, nextRef)

-- | Collects instructions starting from an 'IndigoM'.
-- Returns an 'InstrCollector' as well as the return value for that 'IndigoM'.
instrCollect :: RefId -> SequentialHooks -> IndigoM a -> (a, InstrCollector)
instrCollect ref hooks (IndigoM imCode) =
  let instrColl = InstrCollector ref [] hooks
      (res, resColl) = usingState instrColl $ interpretProgram collectStatement imCode
  in (res, InstrCollector (nextRef resColl) (reverse $ instrList resColl) hooks)

-- | Collects instructions starting from 'S.StatementF'.
-- IMPORTANT: the instructions are collected in the opposite order (as a stack).
collectStatement :: S.StatementF IndigoM a -> State InstrCollector a
collectStatement = \case
  S.LiftIndigoState is -> appendNewInstr $ LiftIndigoState is
  S.CalledFrom callStk iM -> do
    InstrCollector nRef _prevInstrs hooks <- get
    let (res, inner) = instrCollect nRef hooks iM
    modify $ \s -> s {nextRef = nextRef inner}
    res <$ shStmtHook hooks callStk (instrList inner)
  S.NewVar ex -> do
    var <- mkNextVar
    appendNewInstr $ AssignVar var ex
    return var
  S.SetVar vx ex -> appendNewInstr $ SetVar vx ex
  S.VarModification upd vx ey -> appendNewInstr $ VarModification upd vx ey
  S.SetField vStore fname exF -> appendNewInstr $ SetField vStore fname exF

  S.LambdaCall1 lKind lName vIm ex -> withLambdaKind lKind $ do
    (var, block, ret, retVars) <- collectInLambda vIm
    appendNewInstr $ LambdaCall1 lKind lName ex var block ret retVars
    return retVars

  S.Scope (iM :: IndigoM ret) -> do
    retVars <- allocateVars @ret mkNextVar
    (ret, block) <- collectInner iM
    appendNewInstr $ Scope block ret retVars
    return retVars
  S.If ex (iMa :: IndigoM ret) iMb -> do
    retVars <- allocateVars @ret mkNextVar
    (retA, blockA) <- collectInner iMa
    (retB, blockB) <- collectInner iMb
    appendNewInstr $ If ex blockA retA blockB retB retVars
    return retVars
  S.IfSome ex (vIMa :: Var x -> IndigoM ret) iMb -> do
    retVars <- allocateVars @ret mkNextVar
    varX <- mkNextVar
    (retA, blockA) <- collectInner $ vIMa varX
    (retB, blockB) <- collectInner iMb
    appendNewInstr $ IfSome ex varX blockA retA blockB retB retVars
    return retVars
  S.IfRight ex (vIMa :: Var x -> IndigoM ret) vIMb -> do
    retVars <- allocateVars @ret mkNextVar
    varR <- mkNextVar
    (retA, blockA) <- collectInner $ vIMa varR
    varL <- mkNextVar
    (retB, blockB) <- collectInner $ vIMb varL
    appendNewInstr $ IfRight ex varR blockA retA varL blockB retB retVars
    return retVars
  S.IfCons ex (vvIMa :: Var x -> Var (List x) -> IndigoM ret) iMb -> do
    retVars <- allocateVars @ret mkNextVar
    varX <- mkNextVar
    varLX <- mkNextVar
    (retA, blockA) <- collectInner $ vvIMa varX varLX
    (retB, blockB) <- collectInner iMb
    appendNewInstr $ IfCons ex varX varLX blockA retA blockB retB retVars
    return retVars

  S.Case grd clauses -> do
    retVars <- allocateClausesVars clauses
    blockClauses <- collectClauses clauses
    appendNewInstr $ Case grd blockClauses retVars
    return retVars
  S.EntryCase proxy grd clauses -> do
    retVars <- allocateClausesVars clauses
    blockClauses <- collectClauses clauses
    appendNewInstr $ EntryCase proxy grd blockClauses retVars
    return retVars
  S.EntryCaseSimple grd clauses -> do
    retVars <- allocateClausesVars clauses
    blockClauses <- collectClauses clauses
    appendNewInstr $ EntryCaseSimple grd blockClauses retVars
    return retVars

  S.While ex iM -> do
    ((), block) <- collectInner iM
    appendNewInstr $ While ex block
  S.WhileLeft ex vIm -> do
    varL <- mkNextVar
    varR <- mkNextVar
    ((), block) <- collectInner $ vIm varL
    appendNewInstr $ WhileLeft ex varL block varR
    return varR
  S.ForEach ex vIm -> do
    varIop <- mkNextVar
    ((), block) <- collectInner $ vIm varIop
    appendNewInstr $ ForEach ex varIop block

  S.ContractName tx iM -> do
    ((), block) <- collectInner iM
    appendNewInstr $ ContractName tx block
  S.DocGroup dg iM -> do
    ((), block) <- collectInner iM
    appendNewInstr $ DocGroup dg block
  S.ContractGeneral iM -> do
    ((), block) <- collectInner iM
    appendNewInstr $ ContractGeneral block
  S.FinalizeParamCallingDoc vIm param -> do
    varCp <- mkNextVar
    ((), block) <- collectInner $ vIm varCp
    appendNewInstr $ FinalizeParamCallingDoc varCp block param

  S.TransferTokens ex exm exc ->
    appendNewInstr $ TransferTokens ex exm exc
  S.SetDelegate ex ->
    appendNewInstr $ SetDelegate ex
  S.CreateContract ctrc exk exm exs -> do
    varAddr <- mkNextVar
    appendNewInstr $ CreateContract ctrc exk exm exs varAddr
    return varAddr
  S.SelfCalling proxy ep -> do
    varCR <- mkNextVar
    appendNewInstr $ SelfCalling proxy ep varCR
    return varCR
  S.ContractCalling proxy epRef exAddr -> do
    varMcr <- mkNextVar
    appendNewInstr $ ContractCalling proxy epRef exAddr varMcr
    return varMcr

  S.Fail (_ :: Proxy ret) failure -> do
    appendNewInstr $ Fail failure
    -- Note: because this is a failing instr, this vars are effectively never used
    allocateVars @ret mkNextVar
  S.FailOver (_ :: Proxy ret) failure ex -> do
    appendNewInstr $ FailOver failure ex
    -- Note: because this is a failing instr, this vars are effectively never used
    allocateVars @ret mkNextVar

-- | Continues collecting 'Instruction's from an inner 'IndigoM' (e.g. scoped).
-- This keeps advancing the ref counter as well.
collectInner :: IndigoM ret -> State InstrCollector (ret, Block)
collectInner iM = do
  iColl <- get
  let (ret, InstrCollector newRef block _) = instrCollect (nextRef iColl) (seqHooks iColl) iM
  put $ iColl {nextRef = newRef}
  return (ret, block)

-- | Just a common set of steps used by collection of single-arg lambda's values.
collectInLambda
  :: ScopeCodeGen ret
  => (Var arg -> IndigoM ret)
  -> State InstrCollector (Var arg, Block, ret, RetVars ret)
collectInLambda vIm = do
  var <- mkNextVar
  (ret :: ret, block) <- collectInner $ vIm var
  retVars <- allocateVars @ret mkNextVar
  return (var, block, ret, retVars)

-- | Append a new 'Instruction' to the head of the list in the state.
appendNewInstr :: Instruction -> State InstrCollector ()
appendNewInstr is = modify $ \iColl -> iColl {instrList = is : instrList iColl}

appendNewInstrs :: Block -> State InstrCollector ()
appendNewInstrs blk = modify $ \iColl -> iColl {instrList = blk ++ instrList iColl}

-- | Creates a new var. This simply advances the ref counter and updates it.
mkNextVar :: State InstrCollector (Var a)
mkNextVar = do
  iColl <- get
  let ref = nextRef iColl
  put $ iColl {nextRef = ref + 1}
  return $ Var ref

-- | Translation from a 'Block' and an initial 'MetaData' to Lorentz.
sequentialToLorentz
  :: MetaData inp
  -> (Block, RefId)
  -> inp :-> inp
sequentialToLorentz md block =
  runSIS (sequentialToSIS block) md St.cleanGenCode

-- | Translation from a 'Block' to a 'SomeIndigoState'.
sequentialToSIS :: (Block, RefId) -> SomeIndigoState inp
sequentialToSIS ([], _) = toSIS St.nopState
sequentialToSIS (x : xs, refId) = instrToSIS refId x `thenSIS` sequentialToSIS (xs, refId)

-- | Translation from a single 'Instruction' to a 'SomeIndigoState'.
instrToSIS :: RefId -> Instruction -> SomeIndigoState inp
instrToSIS nextRef = \case
  LiftIndigoState sis -> sis
  Comment txt -> toSIS $ comment $ MT.JustComment txt
  AssignVar vx ex -> toSIS $ assignVar vx ex
  SetVar vx ex -> toSIS $ setVar nextRef vx ex
  VarModification upd vx ey -> toSIS $ updateVar nextRef upd vx ey
  SetField vSt lName ex -> toSIS $ setField nextRef vSt lName ex

  LambdaCall1 lKind _lName ex var block ret retVars ->
    withLambdaKind lKind $
      toSIS $ scope (sequentialToSIS (AssignVar var ex : block, nextRef)) ret retVars
  CreateLambda1 lamMd _var body ret varLam ->
    toSIS $ createLambda1Generic varLam ret lamMd (sequentialToSIS (body, nextRef))
  ExecLambda1 lKind (Proxy :: Proxy ret) ex varLam retVars ->
    toSIS $ executeLambda1 @ret lKind nextRef retVars varLam ex

  Scope block ret retVars ->
    toSIS $ scope (sequentialToSIS (block, nextRef)) ret retVars
  If ex blockA retA blockB retB retVars ->
    toSIS $ if_ ex (sequentialToSIS (blockA, nextRef)) retA (sequentialToSIS (blockB, nextRef)) retB retVars
  IfSome ex varX blockA retA blockB retB retVars ->
    toSIS $ ifSome ex varX (sequentialToSIS (blockA, nextRef)) retA (sequentialToSIS (blockB, nextRef)) retB retVars
  IfRight ex varR blockA retA varL blockB retB retVars ->
    toSIS $ ifRight ex varR (sequentialToSIS (blockA, nextRef)) retA varL (sequentialToSIS (blockB, nextRef)) retB retVars
  IfCons ex varX varLX blockA retA blockB retB retVars ->
    toSIS $ ifCons ex varX varLX (sequentialToSIS (blockA, nextRef)) retA (sequentialToSIS (blockB, nextRef)) retB retVars

  Case grd blockClauses retVars ->
    toSIS $ caseRec grd (clausesToBackend nextRef blockClauses) retVars
  EntryCase proxy grd blockClauses retVars ->
    toSIS $ entryCaseRec proxy grd (clausesToBackend nextRef blockClauses) retVars
  EntryCaseSimple grd blockClauses retVars ->
    toSIS $ entryCaseSimpleRec grd (clausesToBackend nextRef blockClauses) retVars

  While ex block ->
    toSIS $ while ex (sequentialToSIS (block, nextRef))
  WhileLeft ex varL block varR ->
    toSIS $ whileLeft ex varL (sequentialToSIS (block, nextRef)) varR
  ForEach ex varIop block ->
    toSIS $ forEach ex varIop (sequentialToSIS (block, nextRef))

  ContractName tx block ->
    contractName tx (sequentialToSIS (block, nextRef))
  DocGroup dg block ->
    docGroup dg (sequentialToSIS (block, nextRef))
  ContractGeneral block ->
    contractGeneral (sequentialToSIS (block, nextRef))
  FinalizeParamCallingDoc varCp block param ->
    finalizeParamCallingDoc varCp (sequentialToSIS (block, nextRef)) param

  TransferTokens ex exm exc ->
    toSIS $ transferTokens ex exm exc
  SetDelegate ex ->
    toSIS $ setDelegate ex
  CreateContract ctrc exk exm exs varAddr ->
    toSIS $ createContract ctrc exk exm exs varAddr
  SelfCalling (Proxy :: Proxy p) ep varCR ->
    toSIS $ selfCalling @p ep varCR
  ContractCalling (Proxy :: Proxy cp) epRef exAddr varMcr ->
    toSIS $ contractCalling @cp epRef exAddr varMcr

  Fail failure -> failure
  FailOver failure ex -> failure ex

----------------------------------------------------------------------------
-- Case machinery
----------------------------------------------------------------------------

-- | Common constraint for case-like 'Instruction's.
type CaseCommon dt ret clauses = CaseCommonF IndigoSeqCaseClause dt ret clauses

-- | Analogous datatype as 'IndigoCaseClauseL' and 'IndigoMCaseClauseL'.
data IndigoSeqCaseClause ret (param :: CaseClauseParam) where
  OneFieldIndigoSeqCaseClause
    :: (AppendSymbol "c" ctor ~ name)
    => Label name
    -> CaseBranch x ret
    -> IndigoSeqCaseClause ret ('CaseClauseParam ctor ('OneField x))

-- | Representation of a branch of a generic case-like 'Instruction'.
data CaseBranch x ret where
  CaseBranch
    :: ( KnownValue x
       , ScopeCodeGen retBr
       , ret ~ RetExprs retBr
       , RetOutStack ret ~ RetOutStack retBr
       )
    => Var x
    -- ^ Input variable (accessible to the branch's code block)
    -> Block
    -- ^ Code block for this branch
    -> retBr
    -- ^ Return value of this branch
    -> CaseBranch x ret

-- | Convert clauses from their "sequential" representation to the "backend" one.
clausesToBackend
  :: forall ret dt . RMap dt
  => RefId
  -> Rec (IndigoSeqCaseClause ret) dt
  -> Rec (IndigoCaseClauseL ret) dt
clausesToBackend nextRef = rmap $
  \(OneFieldIndigoSeqCaseClause cName (CaseBranch vx block ret)) ->
    cName /-> (IndigoClause vx (sequentialToSIS (block, nextRef)) ret)

-- | Allocate vars for the return value(s) of a clause-like 'Instruction'.
allocateClausesVars
  :: forall ret dt. ReturnableValue ret
  => Rec (S.IndigoMCaseClauseL IndigoM ret) dt
  -> State InstrCollector (RetVars ret)
allocateClausesVars _ = allocateVars @ret mkNextVar

-- | Collects clauses of a case-like statement.
collectClauses
  :: Rec (S.IndigoMCaseClauseL IndigoM ret) dt
  -> State InstrCollector (Rec (IndigoSeqCaseClause ret) dt)
collectClauses RNil = return RNil
collectClauses ((S.OneFieldIndigoMCaseClauseL cName clause) :& xs) = do
  varX <- mkNextVar
  (ret, block) <- collectInner $ clause varX
  let clauseX = OneFieldIndigoSeqCaseClause cName (CaseBranch varX block ret)
  clauseXs <- collectClauses xs
  return $ clauseX :& clauseXs

-- | Applies the given 'Block' to 'Block' transformation to the inner code block
-- of every case clause.
updateClauses
  :: (Block -> Block)
  -> Rec (IndigoSeqCaseClause ret) dt
  -> Rec (IndigoSeqCaseClause ret) dt
updateClauses _ RNil = RNil
updateClauses f (x :& xs) = case x of
  OneFieldIndigoSeqCaseClause cName (CaseBranch vx block ret) ->
    OneFieldIndigoSeqCaseClause cName (CaseBranch vx (f block) ret)
      :& updateClauses f xs

-- | Applies the given monadic function giving it the inner code block of each
-- case clause, in order.
mapMClauses :: Monad m => (Block -> m ()) -> Rec (IndigoSeqCaseClause ret) dt -> m ()
mapMClauses _ RNil = return ()
mapMClauses f (x :& xs) = case x of
  OneFieldIndigoSeqCaseClause _cName (CaseBranch _ block _) ->
    f block >> mapMClauses f xs

makeLensesFor [ ("shStmtHook", "stmtHookL")] ''SequentialHooks