keiki-0.2.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.TypeLits (KnownSymbol)
import Keiki.Core
newtype MsgA = MsgA Int deriving stock (Eq, Show)
newtype MsgB = MsgB Int deriving stock (Eq, Show)
newtype MsgC = MsgC Int deriving stock (Eq, Show)
newtype MsgD = MsgD Int deriving stock (Eq, Show)
-- | 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)]
mkInCtor :: String -> (msg -> Int) -> (Int -> msg) -> InCtor msg PayloadSchema
mkInCtor name unwrap rebuild =
InCtor
{ icName = name,
icMatch = \m -> Just (RCons (Proxy @"payload") (unwrap m) RNil),
icBuild = \(RCons _ n RNil) -> rebuild n
}
mkWireCtor :: String -> (msg -> Int) -> (Int -> msg) -> WireCtor msg (Int, ())
mkWireCtor name unwrap rebuild =
WireCtor
{ wcName = name,
wcMatch = \m -> Just (unwrap m, ()),
wcBuild = \(n, ()) -> rebuild n
}
inMsgA :: InCtor MsgA PayloadSchema
inMsgA = mkInCtor "MsgA" (\(MsgA n) -> n) MsgA
inMsgB :: InCtor MsgB PayloadSchema
inMsgB = mkInCtor "MsgB" (\(MsgB n) -> n) MsgB
inMsgC :: InCtor MsgC PayloadSchema
inMsgC = mkInCtor "MsgC" (\(MsgC n) -> n) MsgC
inMsgD :: InCtor MsgD PayloadSchema
inMsgD = mkInCtor "MsgD" (\(MsgD n) -> n) MsgD
wireMsgB :: WireCtor MsgB (Int, ())
wireMsgB = mkWireCtor "MsgB" (\(MsgB n) -> n) MsgB
wireMsgC :: WireCtor MsgC (Int, ())
wireMsgC = mkWireCtor "MsgC" (\(MsgC n) -> n) MsgC
wireMsgD :: WireCtor MsgD (Int, ())
wireMsgD = mkWireCtor "MsgD" (\(MsgD n) -> n) 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
}
],
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
(mkWireCtor "MsgDOut" (\(MsgD n) -> n) MsgD)
id