@@ -16,7 +16,7 @@ module Codec.CBOR.Cuddle.CBOR.Gen (generateCBORTerm) where
16
16
import Capability.Reader
17
17
import Capability.Sink (HasSink )
18
18
import Capability.Source (HasSource , MonadState (.. ))
19
- import Capability.State (HasState , state )
19
+ import Capability.State (HasState , get , modify , state )
20
20
import Codec.CBOR.Cuddle.CDDL
21
21
( Name (.. ),
22
22
OccurrenceIndicator (.. ),
@@ -37,6 +37,7 @@ import Data.ByteString (ByteString)
37
37
import Data.ByteString.Base16 qualified as Base16
38
38
import Data.Functor ((<&>) )
39
39
import Data.Functor.Identity (Identity (runIdentity ))
40
+ import Data.List (foldl' )
40
41
import Data.List.NonEmpty qualified as NE
41
42
import Data.Map.Strict qualified as Map
42
43
import Data.Maybe (fromMaybe )
@@ -67,9 +68,14 @@ data GenEnv g = GenEnv
67
68
}
68
69
deriving (Generic )
69
70
70
- newtype GenState g = GenState
71
+ data GenState g = GenState
71
72
{ -- | 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
73
79
}
74
80
deriving (Generic )
75
81
@@ -81,6 +87,12 @@ newtype M g a = M {runM :: StateT (GenState g) (Reader (GenEnv g)) a}
81
87
" randomSeed"
82
88
()
83
89
(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 ))))
84
96
deriving
85
97
( HasSource " cddl" (CTreeRoot' Identity MonoRef ),
86
98
HasReader " cddl" (CTreeRoot' Identity MonoRef )
@@ -125,6 +137,25 @@ asksM f = f =<< ask @tag
125
137
genUniformRM :: forall a g . (UniformRange a , RandomGen g ) => (a , a ) -> M g a
126
138
genUniformRM = asksM @ " fakeSeed" . uniformRM
127
139
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
+
128
159
genRandomM :: forall g a . (Random a , RandomGen g ) => M g a
129
160
genRandomM = asksM @ " fakeSeed" randomM
130
161
@@ -333,6 +364,8 @@ resolveIfRef :: (RandomGen g) => CTree.Node MonoRef -> M g (CTree MonoRef)
333
364
resolveIfRef (MIt a) = pure a
334
365
resolveIfRef (MRuleRef n) = do
335
366
(CTreeRoot cddl) <- ask @ " cddl"
367
+ -- Since we follow a reference, we increase the 'depth' of the gen monad.
368
+ modify @ " depth" (+ 1 )
336
369
case Map. lookup n cddl of
337
370
Nothing -> error " Unbound reference"
338
371
Just val -> resolveIfRef $ runIdentity val
@@ -366,17 +399,17 @@ applyOccurenceIndicator ::
366
399
M g WrappedTerm ->
367
400
M g WrappedTerm
368
401
applyOccurenceIndicator OIOptional oldGen =
369
- genRandomM >>= \ case
402
+ genDepthBiasedBool >>= \ case
370
403
False -> pure $ G mempty
371
404
True -> oldGen
372
405
applyOccurenceIndicator OIZeroOrMore oldGen =
373
- genUniformRM (0 :: Int , 10 ) >>= \ i ->
406
+ genDepthBiasedRM (0 :: Int , 10 ) >>= \ i ->
374
407
G <$> replicateM i oldGen
375
408
applyOccurenceIndicator OIOneOrMore oldGen =
376
- genUniformRM (1 :: Int , 10 ) >>= \ i ->
409
+ genDepthBiasedRM (1 :: Int , 10 ) >>= \ i ->
377
410
G <$> replicateM i oldGen
378
411
applyOccurenceIndicator (OIBounded mlb mub) oldGen =
379
- genUniformRM (fromMaybe 0 mlb :: Word64 , fromMaybe 10 mub )
412
+ genDepthBiasedRM (fromMaybe 0 mlb :: Word64 , fromMaybe 10 mub )
380
413
>>= \ i -> G <$> replicateM (fromIntegral i) oldGen
381
414
382
415
genValue :: (RandomGen g ) => Value -> M g Term
@@ -398,5 +431,5 @@ genValue (VBytes b) = case Base16.decode b of
398
431
generateCBORTerm :: (RandomGen g ) => CTreeRoot' Identity MonoRef -> Name -> g -> Term
399
432
generateCBORTerm cddl n stdGen =
400
433
let genEnv = GenEnv {cddl, fakeSeed = CapGenM }
401
- genState = GenState {randomSeed = stdGen}
434
+ genState = GenState {randomSeed = stdGen, depth = 1 }
402
435
in evalGen (genForName n) genEnv genState
0 commit comments