tilia-0.0.1.0: tests/Tilia/Gen.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Generators for documents and spans.
module Tilia.Gen
( AnyDoc (..),
PlainDoc (..),
FlatSafeDoc (..),
AnySpan (..),
SingleLineSpan (..),
MultiLineSpan (..),
docTexts,
)
where
import Data.Text (Text)
import Data.Text qualified as T
import Test.QuickCheck
import Tilia.Doc.Internal
import Tilia.Span
----------------------------------------------------------------------------
-- Spans
-- | An arbitrary span.
newtype AnySpan = AnySpan Span
deriving (Eq, Show)
instance Arbitrary AnySpan where
arbitrary = AnySpan <$> genSpan
shrink (AnySpan s) = AnySpan <$> shrinkSpan s
-- | A span that occupied one line.
newtype SingleLineSpan = SingleLineSpan Span
deriving (Eq, Show)
instance Arbitrary SingleLineSpan where
arbitrary = do
l <- choose (1, 20)
c0 <- choose (1, 40)
c1 <- choose (c0, 80)
pure (SingleLineSpan (mkSpan (l, c0) (l, c1)))
-- | A span that ran across several lines.
newtype MultiLineSpan = MultiLineSpan Span
deriving (Eq, Show)
instance Arbitrary MultiLineSpan where
arbitrary = do
l0 <- choose (1, 20)
n <- choose (1, 5)
c0 <- choose (1, 40)
c1 <- choose (1, 80)
pure (MultiLineSpan (mkSpan (l0, c0) (l0 + n, c1)))
genSpan :: Gen Span
genSpan = do
l0 <- choose (1, 20)
n <- choose (0, 5)
c0 <- choose (1, 40)
c1 <- choose (1, 80)
pure (mkSpan (l0, c0) (l0 + n, c1))
shrinkSpan :: Span -> [Span]
shrinkSpan s =
[ mkSpan (spanStartLine s, spanStartColumn s) (spanStartLine s, spanEndColumn s)
| spanStartLine s /= spanEndLine s
]
----------------------------------------------------------------------------
-- Documents
-- | Text for a 'DText' node.
genText :: Gen Text
genText = T.pack <$> resize 4 (listOf1 (elements "abcxyz(),;"))
-- | Any document at all.
newtype AnyDoc = AnyDoc Doc
deriving (Eq, Show)
instance Arbitrary AnyDoc where
arbitrary = AnyDoc <$> sized (genDoc True True)
shrink (AnyDoc d) = AnyDoc <$> shrinkDoc d
-- | A document with no 'DVariant'.
newtype PlainDoc = PlainDoc Doc
deriving (Eq, Show)
instance Arbitrary PlainDoc where
arbitrary = PlainDoc <$> sized (genDoc False True)
shrink (PlainDoc d) = PlainDoc <$> shrinkDoc d
-- | A document that cannot break on its own.
newtype FlatSafeDoc = FlatSafeDoc Doc
deriving (Eq, Show)
instance Arbitrary FlatSafeDoc where
arbitrary = FlatSafeDoc <$> sized (genDoc False False)
shrink (FlatSafeDoc d) = FlatSafeDoc <$> shrinkDoc d
-- | Build a document.
genDoc ::
-- | Whether 'DVariant' may appear
Bool ->
-- | Whether things that force a break may appear
Bool ->
-- | The size parameter
Int ->
Gen Doc
genDoc withVariant withBreaks = go
where
go n
| n <= 1 = leaf
| otherwise = oneof (leaf : branches)
where
half = n `div` 2
branches =
[ DCat <$> go half <*> go half,
DNest <$> choose (0, 2) <*> go (n - 1),
DAlign <$> go (n - 1),
DLocated <$> genSpan <*> go (n - 1),
DFence <$> genSpan <*> go (n - 1)
]
<> [ DGroup <$> elements [Flat, Broken] <*> go (n - 1)
| withBreaks
]
<> [ DVariant <$> go half <*> go half
| withVariant
]
leaf =
oneof $
[ pure DEmpty,
DText <$> genText,
pure DSpace,
pure DBreak,
pure DSoftBreak
]
<> (if withBreaks then [pure DHardBreak] else [])
shrinkDoc :: Doc -> [Doc]
shrinkDoc = \case
DEmpty -> []
DText t -> DText <$> filter (not . T.null) (T.inits t)
DSpace -> [DEmpty]
DBreak -> [DEmpty, DSpace]
DSoftBreak -> [DEmpty]
DHardBreak -> [DEmpty]
DVerbatimBreak _ -> [DEmpty]
DCloseLine -> [DEmpty]
DHoldBack t -> DHoldBack <$> filter (not . T.null) (T.inits t)
DCat a b -> [DEmpty, a, b] <> [DCat a' b | a' <- shrinkDoc a] <> [DCat a b' | b' <- shrinkDoc b]
DNest n d -> [DEmpty, d] <> [DNest n d' | d' <- shrinkDoc d]
DAlign d -> [DEmpty, d] <> [DAlign d' | d' <- shrinkDoc d]
DGroup l d -> [DEmpty, d] <> [DGroup l d' | d' <- shrinkDoc d]
DVariant a b -> [DEmpty, a, b]
DLocated s d -> [DEmpty, d] <> [DLocated s d' | d' <- shrinkDoc d]
DFence s d -> [DEmpty, d] <> [DFence s d' | d' <- shrinkDoc d]
DCppChoice bs e -> [DEmpty, e] <> map snd bs
DCppDirective _ _ -> [DEmpty]
-- | Every fragment of literal text the document contains, in order.
--
-- Undefined in the presence of 'DVariant', which contributes one of two
-- possible sequences depending on a layout this function cannot see; that
-- is what 'PlainDoc' exists to exclude.
docTexts :: Doc -> [Text]
docTexts = \case
DEmpty -> []
DText t -> [t]
DSpace -> []
DBreak -> []
DSoftBreak -> []
DHardBreak -> []
DVerbatimBreak _ -> []
DCloseLine -> []
DHoldBack t -> [t]
DCat a b -> docTexts a <> docTexts b
DNest _ d -> docTexts d
DAlign d -> docTexts d
DGroup _ d -> docTexts d
DVariant a _ -> docTexts a
DLocated _ d -> docTexts d
DFence _ d -> docTexts d
DCppChoice bs e -> concat [c : docTexts d | (c, d) <- bs] <> docTexts e
DCppDirective _ t -> [t]