Skip to content

Commit 4fd069a

Browse files
committed
Control depth of generators.
This commit adds a depth parameter to the CBOR generator, and attempts (somewhat) to prevent non-terminating generation. As the depth increases, we generate smaller lists and try to avoid generating optional elements.
1 parent effb614 commit 4fd069a

File tree

2 files changed

+42
-9
lines changed

2 files changed

+42
-9
lines changed

cuddle.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 3.4
22
name: cuddle
3-
version: 0.1.20.0
3+
version: 0.1.21.0
44
synopsis: CDDL Generator and test utilities
55

66
-- description:

src/Codec/CBOR/Cuddle/CBOR/Gen.hs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module Codec.CBOR.Cuddle.CBOR.Gen (generateCBORTerm) where
1616
import Capability.Reader
1717
import Capability.Sink (HasSink)
1818
import Capability.Source (HasSource, MonadState (..))
19-
import Capability.State (HasState, state)
19+
import Capability.State (HasState, get, modify, state)
2020
import Codec.CBOR.Cuddle.CDDL
2121
( Name (..),
2222
OccurrenceIndicator (..),
@@ -37,6 +37,7 @@ import Data.ByteString (ByteString)
3737
import Data.ByteString.Base16 qualified as Base16
3838
import Data.Functor ((<&>))
3939
import Data.Functor.Identity (Identity (runIdentity))
40+
import Data.List (foldl')
4041
import Data.List.NonEmpty qualified as NE
4142
import Data.Map.Strict qualified as Map
4243
import Data.Maybe (fromMaybe)
@@ -67,9 +68,14 @@ data GenEnv g = GenEnv
6768
}
6869
deriving (Generic)
6970

70-
newtype GenState g = GenState
71+
data GenState g = GenState
7172
{ -- | Actual seed
72-
randomSeed :: g
73+
randomSeed :: g,
74+
-- | Depth of the generator. This measures the number of references we
75+
-- follow. As we go deeper into the tree, we try to reduce the likelihood of
76+
-- following recursive paths, and generate shorter lists where allowed by
77+
-- the occurrence bounds.
78+
depth :: Int
7379
}
7480
deriving (Generic)
7581

@@ -81,6 +87,12 @@ newtype M g a = M {runM :: StateT (GenState g) (Reader (GenEnv g)) a}
8187
"randomSeed"
8288
()
8389
(MonadState (StateT (GenState g) (Reader (GenEnv g))))
90+
deriving
91+
(HasSource "depth" Int, HasSink "depth" Int, HasState "depth" Int)
92+
via Field
93+
"depth"
94+
()
95+
(MonadState (StateT (GenState g) (Reader (GenEnv g))))
8496
deriving
8597
( HasSource "cddl" (CTreeRoot' Identity MonoRef),
8698
HasReader "cddl" (CTreeRoot' Identity MonoRef)
@@ -125,6 +137,25 @@ asksM f = f =<< ask @tag
125137
genUniformRM :: forall a g. (UniformRange a, RandomGen g) => (a, a) -> M g a
126138
genUniformRM = asksM @"fakeSeed" . uniformRM
127139

140+
-- | Generate a random number in a given range, biased increasingly towards the
141+
-- lower end as the depth parameter increases.
142+
genDepthBiasedRM ::
143+
forall a g.
144+
(Ord a, UniformRange a, RandomGen g) =>
145+
(a, a) ->
146+
M g a
147+
genDepthBiasedRM bounds = do
148+
fs <- ask @"fakeSeed"
149+
d <- get @"depth"
150+
samples <- replicateM d (uniformRM bounds fs)
151+
pure $ minimum samples
152+
153+
-- | Generates a bool, increasingly likely to be 'False' as the depth increases.
154+
genDepthBiasedBool :: forall g. (RandomGen g) => M g Bool
155+
genDepthBiasedBool = do
156+
d <- get @"depth"
157+
foldl' (&&) True <$> replicateM d genRandomM
158+
128159
genRandomM :: forall g a. (Random a, RandomGen g) => M g a
129160
genRandomM = asksM @"fakeSeed" randomM
130161

@@ -333,6 +364,8 @@ resolveIfRef :: (RandomGen g) => CTree.Node MonoRef -> M g (CTree MonoRef)
333364
resolveIfRef (MIt a) = pure a
334365
resolveIfRef (MRuleRef n) = do
335366
(CTreeRoot cddl) <- ask @"cddl"
367+
-- Since we follow a reference, we increase the 'depth' of the gen monad.
368+
modify @"depth" (+ 1)
336369
case Map.lookup n cddl of
337370
Nothing -> error "Unbound reference"
338371
Just val -> resolveIfRef $ runIdentity val
@@ -366,17 +399,17 @@ applyOccurenceIndicator ::
366399
M g WrappedTerm ->
367400
M g WrappedTerm
368401
applyOccurenceIndicator OIOptional oldGen =
369-
genRandomM >>= \case
402+
genDepthBiasedBool >>= \case
370403
False -> pure $ G mempty
371404
True -> oldGen
372405
applyOccurenceIndicator OIZeroOrMore oldGen =
373-
genUniformRM (0 :: Int, 10) >>= \i ->
406+
genDepthBiasedRM (0 :: Int, 10) >>= \i ->
374407
G <$> replicateM i oldGen
375408
applyOccurenceIndicator OIOneOrMore oldGen =
376-
genUniformRM (1 :: Int, 10) >>= \i ->
409+
genDepthBiasedRM (1 :: Int, 10) >>= \i ->
377410
G <$> replicateM i oldGen
378411
applyOccurenceIndicator (OIBounded mlb mub) oldGen =
379-
genUniformRM (fromMaybe 0 mlb :: Word64, fromMaybe 10 mub)
412+
genDepthBiasedRM (fromMaybe 0 mlb :: Word64, fromMaybe 10 mub)
380413
>>= \i -> G <$> replicateM (fromIntegral i) oldGen
381414

382415
genValue :: (RandomGen g) => Value -> M g Term
@@ -398,5 +431,5 @@ genValue (VBytes b) = case Base16.decode b of
398431
generateCBORTerm :: (RandomGen g) => CTreeRoot' Identity MonoRef -> Name -> g -> Term
399432
generateCBORTerm cddl n stdGen =
400433
let genEnv = GenEnv {cddl, fakeSeed = CapGenM}
401-
genState = GenState {randomSeed = stdGen}
434+
genState = GenState {randomSeed = stdGen, depth = 1}
402435
in evalGen (genForName n) genEnv genState

0 commit comments

Comments
 (0)