keiki-0.1.0.0: test/Keiki/Render/PrettySpec.hs
-- | Unit tests for "Keiki.Render.Pretty": the domain-readable
-- pretty-printer for 'HsPred' / 'Term' / 'Update'. Pure 'shouldBe'
-- assertions on exact rendered 'Text'.
module Keiki.Render.PrettySpec (spec) where
import Data.Proxy (Proxy (..))
import Data.Text qualified as T
import Keiki.Core
( Cmp (..),
HsPred (..),
InCtor (..),
Index (..),
NumOp (..),
RegFile (..),
Term (..),
Update (..),
)
import Keiki.Internal.Slots (IndexN (..))
import Keiki.Render.Pretty (prettyPred, prettyTerm, prettyUpdate)
import Test.Hspec
-- A two-slot register file schema: "balance" :: Int, "limit" :: Int.
type Regs = '[ '("balance", Int), '("limit", Int)]
-- An input type with one constructor "Deposit" carrying one field
-- "amount" :: Int.
data Cmd = Deposit Int
deriving (Eq, Show)
type DepFields = '[ '("amount", Int)]
inCtorDeposit :: InCtor Cmd DepFields
inCtorDeposit =
InCtor
{ icName = "Deposit",
icMatch = \(Deposit n) -> Just (RCons (Proxy @"amount") n RNil),
icBuild = \(RCons _ n RNil) -> Deposit n
}
-- Index helpers (built by hand so we do not depend on OverloadedLabels
-- resolution here).
balanceIx :: Index Regs Int
balanceIx = ZIdx
limitIx :: Index Regs Int
limitIx = SIdx ZIdx
amountIx :: Index DepFields Int
amountIx = ZIdx
balanceN :: IndexN "balance" Regs Int
balanceN = IZ
spec :: Spec
spec = do
describe "prettyTerm" $ do
it "renders a register read by slot name" $
prettyTerm (TReg balanceIx :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "balance"
it "renders the second register by its slot name" $
prettyTerm (TReg limitIx :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "limit"
it "renders an input-field read as ctor.field" $
prettyTerm (TInpCtorField inCtorDeposit amountIx :: Term Regs Cmd DepFields Int)
`shouldBe` T.pack "Deposit.amount"
it "renders a literal opaquely as <lit>" $
prettyTerm (TLit (42 :: Int) :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "<lit>"
it "renders TApp1 as <fn>(arg)" $
prettyTerm (TApp1 (+ (1 :: Int)) (TReg balanceIx) :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "<fn>(balance)"
it "renders TApp2 as <fn>(a, b)" $
prettyTerm
( TApp2 ((+) :: Int -> Int -> Int) (TReg balanceIx) (TReg limitIx) ::
Term Regs Cmd '[] Int
)
`shouldBe` T.pack "<fn>(balance, limit)"
it "renders TArith add as (a + b)" $
prettyTerm (TArith OpAdd (TReg balanceIx) (TReg limitIx) :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "(balance + limit)"
it "renders TArith sub as (a - b)" $
prettyTerm (TArith OpSub (TReg balanceIx) (TReg limitIx) :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "(balance - limit)"
it "renders TArith mul as (a * b)" $
prettyTerm (TArith OpMul (TReg balanceIx) (TReg limitIx) :: Term Regs Cmd '[] Int)
`shouldBe` T.pack "(balance * limit)"
describe "prettyPred" $ do
it "renders PTop / PBot" $ do
prettyPred (PTop :: HsPred Regs Cmd) `shouldBe` T.pack "true"
prettyPred (PBot :: HsPred Regs Cmd) `shouldBe` T.pack "false"
it "renders PInCtor as the constructor name" $
prettyPred (PInCtor inCtorDeposit :: HsPred Regs Cmd)
`shouldBe` T.pack "Deposit"
it "renders PEq structurally with <lit> on the literal side" $
prettyPred (PEq (TReg balanceIx) (TLit (0 :: Int)) :: HsPred Regs Cmd)
`shouldBe` T.pack "balance == <lit>"
it "renders each PCmp direction" $ do
prettyPred (PCmp CmpLt (TReg balanceIx) (TReg limitIx) :: HsPred Regs Cmd)
`shouldBe` T.pack "balance < limit"
prettyPred (PCmp CmpLe (TReg balanceIx) (TReg limitIx) :: HsPred Regs Cmd)
`shouldBe` T.pack "balance <= limit"
prettyPred (PCmp CmpGt (TReg balanceIx) (TReg limitIx) :: HsPred Regs Cmd)
`shouldBe` T.pack "balance > limit"
prettyPred (PCmp CmpGe (TReg balanceIx) (TReg limitIx) :: HsPred Regs Cmd)
`shouldBe` T.pack "balance >= limit"
it "renders boolean structure with && || !" $
prettyPred
( PAnd
(PInCtor inCtorDeposit)
( POr
(PCmp CmpGe (TReg balanceIx) (TLit (0 :: Int)))
(PNot (PEq (TReg limitIx) (TLit (0 :: Int))))
) ::
HsPred Regs Cmd
)
`shouldBe` T.pack "(Deposit && (balance >= <lit> || !(limit == <lit>)))"
describe "prettyUpdate" $ do
it "renders UKeep" $
prettyUpdate (UKeep :: Update Regs '[] Cmd) `shouldBe` T.pack "(keep)"
it "renders USet as slot := term" $
prettyUpdate (USet balanceN (TLit (0 :: Int)) :: Update Regs '["balance"] Cmd)
`shouldBe` T.pack "balance := <lit>"
it "renders UCombine comma-separated" $
prettyUpdate
( UCombine
(USet balanceN (TReg limitIx))
(USet balanceN (TLit (1 :: Int))) ::
Update Regs '["balance", "balance"] Cmd
)
`shouldBe` T.pack "balance := limit, balance := <lit>"