packages feed

indigo-0.3.0: src/Indigo/Backend/Conditional.hs

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

{-# OPTIONS_GHC -Wno-redundant-constraints #-}

-- | Backend conditional statements of Indigo

module Indigo.Backend.Conditional
  ( if_
  , ifSome
  , ifRight
  , ifCons
  , IfConstraint
  ) where

import qualified Data.Kind as Kind
import qualified GHC.TypeLits as Lit
import Util.Type (type (++))

import Indigo.Backend.Prelude
import Indigo.Backend.Scope
import Indigo.Internal
import Indigo.Lorentz
import qualified Lorentz.Instr as L
import qualified Lorentz.Macro as L

type family CompareBranchesResults (a :: Kind.Type) (b :: Kind.Type) :: Constraint where
  CompareBranchesResults x x = ()
  CompareBranchesResults x y = Lit.TypeError
      ('Lit.Text " Result types of if branches diverged: "
       'Lit.:<>: 'Lit.ShowType x 'Lit.:<>: ('Lit.Text " against ") 'Lit.:<>: 'Lit.ShowType y
      )

type IfConstraint a b =
  ( ScopeCodeGen a
  , ScopeCodeGen b
  , CompareBranchesResults (RetExprs a) (RetExprs b)
  -- These constraints below are implied by the one above, but GHC needs a proof
  , RetVars a ~ RetVars b
  , RetOutStack a ~ RetOutStack b
  )

-- | If statement. All variables created inside its branches will be released
-- after the execution leaves the scope in which they were created.
if_
  :: forall inp a b . IfConstraint a b
  => Expr Bool
  -- ^ Expression for the control flow
  -> SomeIndigoState inp
  -- ^ Code block for the positive branch
  -> a
  -- ^ Return value(s) of the positive branch
  -> SomeIndigoState inp
  -- ^ 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)
  -> IndigoState inp (RetOutStack a ++ inp)
if_ e t retA f retB retVars = IndigoState $ \md@MetaData{..} ->
  let cde = gcCode $ usingIndigoState md (compileExpr e) in
  runSIS t md $ \gc1 ->
    runSIS f md $ \gc2 ->
      finalizeStatement @a mdStack retVars $
        cde # L.if_ (compileScope @a mdObjects gc1 retA) (compileScope @b mdObjects gc2 retB)

-- | If which works like case for Maybe.
ifSome
  :: forall inp x a b . (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)
  -> SomeIndigoState (x & inp)
  -- ^ Code block for the 'Just' branch
  -> a
  -- ^ Return value(s) of the 'Just' branch
  -> SomeIndigoState inp
  -- ^ 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)
  -> IndigoState inp (RetOutStack a ++ inp)
ifSome e varX t retA f retB retVars = IndigoState $ \md@MetaData{..} ->
  let cde = gcCode $ usingIndigoState md (compileExpr e) in
  let mdJust = pushRefMd varX md in
  runSIS t mdJust $ \gc1 ->
    runSIS f md $ \gc2 ->
      finalizeStatement @a mdStack retVars $
        cde #
        L.ifSome
          ( compileScope @a mdObjects gc1 retA #
            -- after this we have stack (e1 & e2 .. & ek & x & inp)
            liftClear' @(ClassifyReturnValue a) @a @(x & inp) @inp L.drop
            -- this can be lifted together with glClear code, but let's leave it like this for now
          )
          (compileScope @b mdObjects gc2 retB)

-- | If which works like case for Either.
ifRight
  :: forall inp r l a b . (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)
  -> SomeIndigoState (r & inp)
  -- ^ 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)
  -> SomeIndigoState (l & inp)
  -- ^ 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)
  -> IndigoState inp (RetOutStack a ++ inp)
ifRight e varR r retA varL l retB retVars = IndigoState $ \md@MetaData{..} ->
  let
    cde = gcCode $ usingIndigoState md (compileExpr e)
    mdRight = pushRefMd varR md
    mdLeft = pushRefMd varL md
  in
    runSIS r mdRight $ \gc1 ->
      runSIS l mdLeft $ \gc2 ->
        finalizeStatement @a mdStack retVars $
          cde #
          L.ifRight
            ( compileScope @a mdObjects gc1 retA #
            -- after this we have stack (e1 & e2 .. & ek & x & inp)
            liftClear' @(ClassifyReturnValue a) @a @(r & inp) @inp L.drop
            -- this can be lifted together with glClear code, but let's leave it like this for now
            )
            ( compileScope @b mdObjects gc2 retB #
            -- after this we have stack (e1 & e2 .. & ek & x & inp)
            liftClear' @(ClassifyReturnValue b) @b @(l & inp) @inp L.drop
            -- this can be lifted together with glClear code, but let's leave it like this for now
            )

-- | If which works like uncons for lists.
ifCons
  :: forall inp x a b . (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)
  -> SomeIndigoState (x & List x & inp)
  -- ^ Code block for the non-empty list branch
  -> a
  -- ^ Return value(s) of the non-empty list branch
  -> SomeIndigoState inp
  -- ^ 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)
  -> IndigoState inp (RetOutStack a ++ inp)
ifCons e vx vlx t retA f retB retVars = IndigoState $ \md@MetaData{..} ->
  let
    cde = gcCode $ usingIndigoState md (compileExpr e)
    mdList = pushRefMd vlx md
    mdVal = pushRefMd vx mdList
  in
    runSIS t mdVal $ \gc1 ->
      runSIS f md $ \gc2 ->
        finalizeStatement @a mdStack retVars $
          cde #
          L.ifCons
            ( compileScope @a mdObjects gc1 retA #
              liftClear' @(ClassifyReturnValue a) @a @(x & List x & inp) @inp (L.drop # L.drop)
            )
            (compileScope @b mdObjects gc2 retB)