keiki-0.9.0.0: test/Keiki/Fixtures/CounterPipeline.hs
-- | Three-stage stateful counter pipeline used by the EP-69 Category
-- and Choice regression tests. See the plan at
-- docs/plans/69-replace-the-fabricated-weakenr-and-knownslotnames-dictionary-in-category-composition-with-real-induction-witnesses.md
-- for the two design constraints (mid-alphabet constructor-name
-- alignment; no cross-stage register reads in substituted update RHSs).
module Keiki.Fixtures.CounterPipeline
( MsgA (..),
MsgB (..),
MsgC (..),
MsgD (..),
StageVertex (..),
ARegs,
BRegs,
CRegs,
stageA,
stageB,
stageC,
stageConflict,
inMsgB,
inMsgC,
inMsgD,
wireMsgB,
wireMsgC,
wireMsgD,
)
where
import Data.Proxy (Proxy (..))
import GHC.Generics (Generic)
import GHC.TypeLits (KnownSymbol)
import Keiki.Core
import Keiki.Generics (mkInCtorRecordVia, mkWireCtorRecordVia)
newtype MsgA = MsgA {payload :: Int} deriving stock (Eq, Show, Generic)
newtype MsgB = MsgB {payload :: Int} deriving stock (Eq, Show, Generic)
newtype MsgC = MsgC {payload :: Int} deriving stock (Eq, Show, Generic)
newtype MsgD = MsgD {payload :: Int} deriving stock (Eq, Show, Generic)
-- | Every stage is a one-vertex machine that loops on itself.
data StageVertex = StageVertex deriving stock (Eq, Ord, Show, Bounded, Enum)
type ARegs = '[ '("regA", Int)]
type BRegs = '[ '("regB", Int)]
type CRegs = '[ '("regC", Int)]
-- | One-field input schema shared by all pipeline messages.
type PayloadSchema = '[ '("payload", Int)]
inMsgA :: InCtor MsgA PayloadSchema
inMsgA = mkInCtorRecordVia @"MsgA"
inMsgB :: InCtor MsgB PayloadSchema
inMsgB = mkInCtorRecordVia @"MsgB"
inMsgC :: InCtor MsgC PayloadSchema
inMsgC = mkInCtorRecordVia @"MsgC"
inMsgD :: InCtor MsgD PayloadSchema
inMsgD = mkInCtorRecordVia @"MsgD"
wireMsgB :: WireCtor MsgB (Int, ())
wireMsgB = mkWireCtorRecordVia @"MsgB"
wireMsgC :: WireCtor MsgC (Int, ())
wireMsgC = mkWireCtorRecordVia @"MsgC"
wireMsgD :: WireCtor MsgD (Int, ())
wireMsgD = mkWireCtorRecordVia @"MsgD"
-- | Shared stage shape: guard reads the register (a real read, always
-- satisfied for this fixture's inputs); update accumulates the input
-- payload into the register; output is the caller-supplied field term.
counterStage ::
forall name inMsg outMsg.
(KnownSymbol name) =>
InCtor inMsg PayloadSchema ->
WireCtor outMsg (Int, ()) ->
( Term '[ '(name, Int)] inMsg PayloadSchema Int ->
Term '[ '(name, Int)] inMsg PayloadSchema Int
) ->
SymTransducer
(HsPred '[ '(name, Int)] inMsg)
'[ '(name, Int)]
StageVertex
inMsg
outMsg
counterStage ic wc mkField =
SymTransducer
{ edgesOut = \StageVertex ->
[ Edge
{ guard =
PAnd
(PInCtor ic)
(PCmp CmpGe (TReg ZIdx) (TLit (0 :: Int))),
update = USet IZ (tadd (TReg ZIdx) (TInpCtorField ic ZIdx)),
output =
[pack ic wc (OFCons (mkField (TInpCtorField ic ZIdx)) OFNil)],
target = StageVertex,
mode = Live
}
],
initial = StageVertex,
initialRegs = RCons (Proxy @name) 0 RNil,
isFinal = const True
}
-- | Stage a: doubles the payload; accumulates inputs into @regA@.
stageA :: SymTransducer (HsPred ARegs MsgA) ARegs StageVertex MsgA MsgB
stageA = counterStage inMsgA wireMsgB (\p -> tmul p (lit 2))
-- | Stage b: increments the payload; accumulates inputs into @regB@.
stageB :: SymTransducer (HsPred BRegs MsgB) BRegs StageVertex MsgB MsgC
stageB = counterStage inMsgB wireMsgC (\p -> tadd p (lit 1))
-- | Stage c adds its own accumulator to the payload. The register
-- read detects a composite dictionary that fails to shift past the
-- upstream slots.
stageC :: SymTransducer (HsPred CRegs MsgC) CRegs StageVertex MsgC MsgD
stageC = counterStage inMsgC wireMsgD (\p -> tadd p (TReg ZIdx))
-- | A @MsgD -> MsgD@ stage that deliberately reuses stage a's slot
-- name. Composing it after a pipeline containing stage a must raise
-- 'Keiki.Profunctor.CategoryOverlapError'.
stageConflict ::
SymTransducer
(HsPred '[ '("regA", Int)] MsgD)
'[ '("regA", Int)]
StageVertex
MsgD
MsgD
stageConflict =
counterStage
inMsgD
(renameWireCtor "MsgDOut" wireMsgD)
id