fir-0.1.0.0: src/FIR/AST/ControlFlow.hs
{-# OPTIONS_GHC -fno-warn-missing-pattern-synonym-signatures -Wno-missing-signatures #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-|
Module: FIR.AST.ControlFlow
AST operations involving control flow (conditional branching, selections, loops).
-}
module FIR.AST.ControlFlow where
-- base
import Data.Kind
( Type )
import Data.Proxy
( Proxy(..) )
import qualified GHC.Stack
( HasCallStack )
import GHC.TypeNats
( KnownNat, natVal )
-- containers
import Data.Tree
( Tree(Node) )
-- variant
import Data.Variant.EGADT
( EGADT, type (:<!), pattern VF )
-- fir
import Control.Arrow.Strength
( secondF )
import FIR.AST.Display
( Display(toTreeArgs) )
import FIR.AST.Type
( AugType(Val, (:-->)), Eff )
import {-# SOURCE #-} FIR.Prim.Types
( PrimTy, IntegralTy )
import FIR.Validation.CFG
( InLoop )
------------------------------------------------------------
pattern If :: forall a fs
. ( GHC.Stack.HasCallStack, PrimTy a, SelectionF :<! fs )
=> EGADT fs ( Val Bool :--> Val a :--> Val a :--> Val a )
pattern If = VF IfF
pattern IfM = VF IfMF
pattern Switch s d cs = VF (SwitchF s d cs)
pattern SwitchM s d cs = VF (SwitchMF s d cs)
pattern While = VF WhileF
pattern Loop = VF LoopF
pattern Break n = VF (BreakF n)
pattern BreakContinue n = VF (BreakContinueF n)
data SelectionF ( ast :: AugType -> Type ) ( t :: AugType ) where
-- | If-then-else statement.
IfF
:: ( GHC.Stack.HasCallStack
, PrimTy a
)
=> SelectionF ast ( Val Bool :--> Val a :--> Val a :--> Val a )
-- | Monadic if-then-else.
IfMF
:: GHC.Stack.HasCallStack
=> SelectionF ast ( Eff i i Bool :--> Eff i j a :--> Eff i k a :--> Eff i i a )
-- | Switch statement.
SwitchF
:: ( GHC.Stack.HasCallStack
, PrimTy a
, IntegralTy s
)
=> ast ( Val s ) -- ^ Scrutinee.
-> ast ( Val a ) -- ^ Default case.
-> [ ( s, ast (Val a) ) ] -- ^ Cases.
-> SelectionF ast ( Val a )
-- | Monadic switch statement.
SwitchMF
:: ( GHC.Stack.HasCallStack
, PrimTy a
, IntegralTy s
)
=> ast ( Eff i i s ) -- ^ Scrutinee.
-> ast ( Eff i i a ) -- ^ Default case.
-> [ ( s, ast ( Eff i i a ) ) ] -- ^ Cases.
-> SelectionF ast ( Eff i i a )
-- ( SwitchM could be generalised by allowing different return types in the branches.)
data LoopF ( ast :: AugType -> Type ) ( t :: AugType ) where
-- | While loop.
WhileF
:: GHC.Stack.HasCallStack
=> LoopF ast ( Eff i i Bool :--> Eff (InLoop i) j () :--> Eff i i () )
-- | Loop.
LoopF
:: GHC.Stack.HasCallStack
=> LoopF ast ( Eff (InLoop i) j () :--> Eff i i () )
-- | Break out of the given number of loops.
BreakF
:: ( KnownNat n, GHC.Stack.HasCallStack )
=> Proxy n
-> LoopF ast ( Eff i i () )
-- | Break out of the given number of loops, and continue from the resulting loop.
BreakContinueF
:: ( KnownNat n, GHC.Stack.HasCallStack )
=> Proxy n
-> LoopF ast ( Eff i i () )
------------------------------------------------------------
-- displaying
instance Display ast => Display (SelectionF ast) where
toTreeArgs as IfF = pure ( Node "If" as )
toTreeArgs as IfMF = pure ( Node "IfM" as )
toTreeArgs as ( SwitchF s d cs ) = do
scrut <- toTreeArgs [] s
def <- toTreeArgs [] d
cases <- traverse ( secondF ( toTreeArgs [] ) ) cs
let
allCases
= map ( \(v,a) -> Node ("Case " ++ show v) [a] ) cases
++ [ Node "Default" [def] ]
return (Node "Switch" (scrut : allCases ++ as))
toTreeArgs as (SwitchMF s d cs) = do
scrut <- toTreeArgs [] s
def <- toTreeArgs [] d
cases <- traverse ( secondF ( toTreeArgs [] ) ) cs
let
allCases
= map ( \(v,a) -> Node ("Case " ++ show v) [a] ) cases
++ [ Node "Default" [def] ]
return (Node "SwitchM" (scrut : allCases ++ as))
instance Display ast => Display (LoopF ast) where
toTreeArgs as WhileF = pure ( Node "While" as )
toTreeArgs as LoopF = pure ( Node "Loop" as )
toTreeArgs as ( BreakF n ) = pure ( Node ( "Break @" ++ show ( natVal n ) ) as )
toTreeArgs as ( BreakContinueF n ) =
let
nm = case natVal n of
0 -> "Continue"
i -> "BreakContinue @" ++ show i
in pure ( Node nm as )