packages feed

cleveland-0.2.0: src/Hedgehog/Gen/Michelson/Untyped.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

module Hedgehog.Gen.Michelson.Untyped
  ( genInternalByteString
  , genVar
  , genTyVar
  , genExpandedOp
  , genExtInstrAbstract
  , genPrintComment
  , genStackRef
  , genTestAssert
  , genStackTypePattern
  , genInstrAbstract
  , genContract
  , genContract'
  , genEntriesOrder
  , genValue
  , genValue'
  , genElt
  , genParameterType
  , genType
  , genEpName
  , genAnnotation
  , genT
  ) where

import Prelude hiding (EQ, GT, LT)

import Data.Text qualified as T
import Hedgehog (MonadGen(GenBase), Range)
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range

import Morley.Michelson.Untyped

import Hedgehog.Gen.Michelson (genErrorSrcPos, genMText)
import Hedgehog.Range.Defaults

genInternalByteString :: MonadGen m => Range Length -> m InternalByteString
genInternalByteString rangeLen = InternalByteString <$> Gen.bytes (unLength <$> rangeLen)

genVar :: MonadGen m => m Var
genVar = Var <$> genSmallText

genTyVar :: (MonadGen m, GenBase m ~ Identity) => m TyVar
genTyVar = Gen.choice [VarID <$> genVar, TyCon <$> genType]

genExpandedOp :: (MonadGen m, GenBase m ~ Identity) => m ExpandedOp
genExpandedOp = Gen.recursive Gen.choice
  -- non-recursive constructors
  [
    -- NB: When used together, genExpandedOp & genInstrAbstract are mutually recursive.
    -- So we use a generator of InstrAbstract that does not contain any ops as an escape hatch.
    PrimEx <$> Gen.choice instrAbstractWithoutOp
  ]
  -- recursive constructors
  [ PrimEx <$> genInstrAbstract genExpandedOp
  , SeqEx <$> genSmallList genExpandedOp
  , Gen.subtermM genExpandedOp $ \expandedOp -> WithSrcEx <$> genErrorSrcPos <*> pure expandedOp
  ]

genExtInstrAbstract :: (MonadGen m, GenBase m ~ Identity) => m op -> m (ExtInstrAbstract op)
genExtInstrAbstract genOp = Gen.choice
  [ STACKTYPE <$> genStackTypePattern
  , UTEST_ASSERT <$> genTestAssert genOp
  , UPRINT <$> genPrintComment def
  , UCOMMENT <$> genSmallText
  ]

genPrintComment :: MonadGen m => Range TinyLength -> m PrintComment
genPrintComment rangeLen = PrintComment <$> Gen.list (unTinyLength <$> rangeLen)
  (Gen.either genSmallText (genStackRef def))

genStackRef :: MonadGen m => Range StackRef -> m StackRef
genStackRef range = StackRef <$> Gen.integral (unStackRef <$> range)
  where unStackRef (StackRef x) = x

genTestAssert :: MonadGen m => m op -> m (TestAssert op)
genTestAssert genOp = TestAssert <$> genSmallText <*> genPrintComment def <*> genSmallList genOp

genStackTypePattern :: (MonadGen m, GenBase m ~ Identity) => m StackTypePattern
genStackTypePattern = Gen.recursive Gen.choice
  [ pure StkEmpty, pure StkRest ]
  [ Gen.subtermM genStackTypePattern $ \stp -> StkCons <$> genTyVar <*> pure stp ]

genInstrAbstract :: (MonadGen m, GenBase m ~ Identity) => m op -> m (InstrAbstract op)
genInstrAbstract genOp =
  Gen.choice $ instrAbstractWithOp genOp <> instrAbstractWithoutOp

instrAbstractWithOp :: (MonadGen m, GenBase m ~ Identity) => m op -> [m (InstrAbstract op)]
instrAbstractWithOp genOp =
  [ EXT <$> genExtInstrAbstract genOp
  , PUSH <$> genAnnotation <*> genType <*> genValue' genOp
  , IF_NONE <$> genSmallList genOp <*> genSmallList genOp
  , IF_LEFT <$> genSmallList genOp <*> genSmallList genOp
  , IF_CONS <$> genSmallList genOp <*> genSmallList genOp
  , MAP <$> genAnnotation <*> genSmallList genOp
  , ITER <$> genSmallList genOp
  , IF <$> genSmallList genOp <*> genSmallList genOp
  , LOOP <$> genSmallList genOp
  , LOOP_LEFT <$> genSmallList genOp
  , LAMBDA <$> genAnnotation <*> genType <*> genType <*> genSmallList genOp
  , DIP <$> genSmallList genOp
  , DIPN <$> Gen.word Range.linearBounded <*> genSmallList genOp
  , CREATE_CONTRACT <$> genAnnotation <*> genAnnotation <*> genContract' genOp
  ]

instrAbstractWithoutOp :: (MonadGen m, GenBase m ~ Identity) => [m (InstrAbstract op)]
instrAbstractWithoutOp =
  [ DROPN <$> Gen.word Range.linearBounded
  , pure DROP
  , DUP <$> genAnnotation
  , DUPN <$> genAnnotation <*> Gen.word Range.linearBounded
  , pure SWAP
  , DIG <$> Gen.word Range.linearBounded
  , DUG <$> Gen.word Range.linearBounded
  , SOME <$> genAnnotation <*> genAnnotation
  , NONE <$> genAnnotation <*> genAnnotation <*> genType
  , UNIT <$> genAnnotation <*> genAnnotation
  , PAIR <$> genAnnotation <*> genAnnotation <*> genAnnotation <*> genAnnotation
  , PAIRN <$> genAnnotation <*> Gen.word Range.linearBounded
  , UNPAIRN <$> Gen.word Range.linearBounded
  , CAR <$> genAnnotation <*> genAnnotation
  , CDR <$> genAnnotation <*> genAnnotation
  , LEFT <$> genAnnotation <*> genAnnotation <*> genAnnotation <*> genAnnotation <*> genType
  , RIGHT <$> genAnnotation <*> genAnnotation <*> genAnnotation <*> genAnnotation <*> genType
  , NIL <$> genAnnotation <*> genAnnotation <*> genType
  , CONS <$> genAnnotation
  , SIZE <$> genAnnotation
  , EMPTY_SET <$> genAnnotation <*> genAnnotation <*> genType
  , EMPTY_MAP <$> genAnnotation <*> genAnnotation <*> genType <*> genType
  , EMPTY_BIG_MAP <$> genAnnotation <*> genAnnotation <*> genType <*> genType
  , MEM <$> genAnnotation
  , GET <$> genAnnotation
  , GETN <$> genAnnotation <*> Gen.word Range.linearBounded
  , UPDATE <$> genAnnotation
  , EXEC <$> genAnnotation
  , APPLY <$> genAnnotation
  , pure FAILWITH
  , CAST <$> genAnnotation <*> genType
  , RENAME <$> genAnnotation
  , PACK <$> genAnnotation
  , UNPACK <$> genAnnotation <*> genAnnotation <*> genType
  , CONCAT <$> genAnnotation
  , SLICE <$> genAnnotation
  , ISNAT <$> genAnnotation
  , ADD <$> genAnnotation
  , SUB <$> genAnnotation
  , SUB_MUTEZ <$> genAnnotation
  , MUL <$> genAnnotation
  , EDIV <$> genAnnotation
  , ABS <$> genAnnotation
  , NEG <$> genAnnotation
  , LSL <$> genAnnotation
  , LSR <$> genAnnotation
  , OR <$> genAnnotation
  , AND <$> genAnnotation
  , XOR <$> genAnnotation
  , NOT <$> genAnnotation
  , COMPARE <$> genAnnotation
  , EQ <$> genAnnotation
  , NEQ <$> genAnnotation
  , LT <$> genAnnotation
  , GT <$> genAnnotation
  , LE <$> genAnnotation
  , GE <$> genAnnotation
  , INT <$> genAnnotation
  , SELF <$> genAnnotation <*> genAnnotation
  , CONTRACT <$> genAnnotation <*> genAnnotation <*> genType
  , TRANSFER_TOKENS <$> genAnnotation
  , SET_DELEGATE <$> genAnnotation
  , IMPLICIT_ACCOUNT <$> genAnnotation
  , NOW <$> genAnnotation
  , AMOUNT <$> genAnnotation
  , BALANCE <$> genAnnotation
  , CHECK_SIGNATURE <$> genAnnotation
  , SHA256 <$> genAnnotation
  , SHA512 <$> genAnnotation
  , BLAKE2B <$> genAnnotation
  , SHA3 <$> genAnnotation
  , KECCAK <$> genAnnotation
  , HASH_KEY <$> genAnnotation
  , SOURCE <$> genAnnotation
  , SENDER <$> genAnnotation
  , ADDRESS <$> genAnnotation
  , CHAIN_ID <$> genAnnotation
  , LEVEL <$> genAnnotation
  , SELF_ADDRESS <$> genAnnotation
  ]

genContract :: (MonadGen m, GenBase m ~ Identity) => m Contract
genContract = genContract' genExpandedOp

genContract' :: (MonadGen m, GenBase m ~ Identity) => m op -> m (Contract' op)
genContract' genOp = Contract
  <$> genParameterType
  <*> genType
  <*> genSmallList genOp
  <*> genEntriesOrder
  <*> Gen.list (Range.exponential 0 2) (genSomeView genOp)

genEntriesOrder :: (MonadGen m) => m EntriesOrder
genEntriesOrder = Gen.enumBounded

genViewName :: (MonadGen m, GenBase m ~ Identity) => m ViewName
genViewName = unsafe . mkViewName <$> Gen.text
  (Range.linear 0 viewNameMaxLength)
  (Gen.filter isValidViewNameChar Gen.ascii)

genSomeView :: (MonadGen m, GenBase m ~ Identity) => m op -> m (View' op)
genSomeView genOp = View
  <$> genViewName
  <*> genType
  <*> genType
  <*> genSmallList genOp

genValue :: (MonadGen m, GenBase m ~ Identity) => m Value
genValue = genValue' genExpandedOp

genValueInt :: MonadGen m => Range ValueInt -> m (Value' op)
genValueInt range = ValueInt <$> Gen.integral (unValueInt <$> range)

genValueString :: MonadGen m => Range Length -> m (Value' op)
genValueString = fmap ValueString . genMText

genValueBytes :: MonadGen m => Range Length -> m (Value' op)
genValueBytes = fmap ValueBytes . genInternalByteString

genValue' :: MonadGen m => m op -> m (Value' op)
genValue' genOp = Gen.recursive Gen.choice
  -- non-recursive constructors
  [ genValueInt def
  , genValueString def
  , genValueBytes def
  , pure ValueUnit
  , pure ValueTrue
  , pure ValueFalse
  , pure ValueNone
  , pure ValueNil
  ]
  -- recursive constructors
  [ Gen.subterm2 (genValue' genOp) (genValue' genOp) ValuePair
  , Gen.subterm (genValue' genOp) ValueLeft
  , Gen.subterm (genValue' genOp) ValueRight
  , Gen.subterm (genValue' genOp) ValueSome
  , ValueSeq <$> genSmallNonEmpty (genValue' genOp)
  , ValueMap <$> genSmallNonEmpty (genElt genOp)
  , ValueLambda <$> genSmallNonEmpty genOp
  ]

genElt :: MonadGen m => m op -> m (Elt op)
genElt genOp = Elt <$> genValue' genOp <*> genValue' genOp

genParameterType :: (MonadGen m, GenBase m ~ Identity) => m ParameterType
genParameterType = ParameterType <$> genType <*> genAnnotation

-- | Generate a 'Ty'.
genType :: (MonadGen m, GenBase m ~ Identity) => m Ty
genType = Ty <$> genT <*> genAnnotation

genComparableType :: (MonadGen m, GenBase m ~ Identity) => m Ty
genComparableType = Ty <$> genComparableT <*> genAnnotation

genEpName :: (MonadGen m, GenBase m ~ Identity) => m EpName
genEpName = Gen.mapMaybe (rightToMaybe . epNameFromRefAnn) genAnnotation

genAnnotation :: forall m a. (MonadGen m, GenBase m ~ Identity) => m (Annotation a)
genAnnotation = Gen.mapMaybe (rightToMaybe . mkAnnotation) genAnnotationText
  where
    genAnnotationText :: m Text
    genAnnotationText = Gen.frequency
      [ (1, pure "")
      , (17, T.cons <$> annStart <*> Gen.text (Range.linear 0 100) annBodyChar)
      , (1, pure specialFieldAnn)
      , (1, Gen.element specialVarAnns)
      ]

    annStart :: m Char
    annStart = Gen.choice [ pure '_', Gen.alphaNum ]

    annBodyChar :: m Char
    annBodyChar = Gen.choice [ annStart, Gen.element (".%@" :: String) ]

-- | Generate a 'T'.
genT :: (MonadGen m, GenBase m ~ Identity) => m T
genT =
  Gen.recursive Gen.choice
    -- non-recursive constructors
    [ pure TKey
    , pure TUnit
    , pure TSignature
    , pure TChainId
    , pure TOperation
    , pure TInt
    , pure TNat
    , pure TString
    , pure TBytes
    , pure TMutez
    , pure TBool
    , pure TKeyHash
    , pure TTimestamp
    , pure TAddress
    , pure TChest
    , pure TChestKey
    ]
    -- recursive constructors
    [ TOption <$> genType
    , TList <$> genType
    , TSet <$> genComparableType
    , TContract <$> genType
    , TPair <$> genAnnotation <*> genAnnotation <*> genAnnotation
            <*> genAnnotation <*> genType <*> genType
    , TOr <$> genAnnotation <*> genAnnotation <*> genType <*> genType
    , TLambda <$> genType <*> genType
    , TMap <$> genComparableType <*> genType
    , TBigMap <$> genComparableType <*> genType
    ]

-- We don't need to pass @mustBeValid@ here because comparable types are always valid.
genComparableT :: (MonadGen m, GenBase m ~ Identity) => m T
genComparableT =
  Gen.recursive Gen.choice
    [ pure TInt
    , pure TNat
    , pure TString
    , pure TBytes
    , pure TMutez
    , pure TBool
    , pure TKeyHash
    , pure TTimestamp
    , pure TAddress
    ]
    [ TPair <$> genAnnotation <*> genAnnotation <*> genAnnotation
            <*> genAnnotation <*> genComparableType <*> genComparableType
    ]

smallCollectionRange :: Range Int
smallCollectionRange = Range.linear 0 3

genSmallList :: MonadGen m => m a -> m [a]
genSmallList = Gen.list smallCollectionRange

genSmallNonEmpty :: MonadGen m => m a -> m (NonEmpty a)
genSmallNonEmpty = Gen.nonEmpty smallCollectionRange

genSmallText :: MonadGen m => m Text
genSmallText = Gen.text (Range.linear 0 10) Gen.unicodeAll