capnp-0.17.0.0: tests/SchemaGeneration.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module SchemaGeneration
( Schema (..),
genSchema,
)
where
import Control.Monad.State.Strict
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE
import qualified Test.QuickCheck as QC
-- Definitions
newtype FieldName
= FieldName String
instance Show FieldName where
show (FieldName fn) = fn
newtype StructName
= StructName String
instance Show StructName where
show (StructName fn) = fn
data Field
= FieldDef FieldName Int FieldType
| StructDef StructName [Field]
data BuiltIn
= Void
| Bool
| Int8
| Int16
| Int32
| Int64
| UInt8
| UInt16
| UInt32
| UInt64
| Float32
| Float64
| Text
| Data
deriving (Show, Enum)
data FieldType
= BasicType BuiltIn
| ListType FieldType
| StructType StructName
instance Show FieldType where
show (BasicType bi) = show bi
show (ListType ft) = "List(" ++ show ft ++ ")"
show (StructType sn) = show sn
instance Show Field where
show (FieldDef name order entryType) =
concat
[ show name,
" @",
show order,
" :",
show entryType,
";\n"
]
show (StructDef name content) =
concat
[ "struct ",
show name,
" {\n",
concatMap (('\t' :) . show) content,
"}\n\n"
]
data Schema = Schema
{ schemaId :: String,
schemaContent :: [Field]
}
instance Show Schema where
show s =
concat
[ "@0x",
schemaId s,
";\n\n",
concatMap show (schemaContent s)
]
-- Helper generators
genSafeLCChar :: QC.Gen Char
genSafeLCChar = QC.elements ['a' .. 'z']
genSafeUCChar :: QC.Gen Char
genSafeUCChar = QC.elements ['A' .. 'Z']
genSafeHexChar :: QC.Gen Char
genSafeHexChar = QC.elements (['0' .. '9'] ++ ['a' .. 'f'])
newtype FieldGen a
= FieldGen (StateT (NonEmpty (Int, Int)) QC.Gen a)
deriving (Functor, Applicative, Monad)
liftGen :: QC.Gen a -> FieldGen a
liftGen m = FieldGen (lift m)
runFieldGen :: FieldGen a -> QC.Gen a
runFieldGen (FieldGen m) = fst <$> runStateT m ((0, 0) :| [])
pushFieldGen :: FieldGen ()
pushFieldGen = FieldGen $ modify (NE.cons (0, 0))
popFieldGen :: FieldGen ()
popFieldGen = FieldGen $ do
original <- get
case original of
(_ :| (y : rest)) -> put (y :| rest)
(x :| []) -> put (x :| [])
getStructOrder :: FieldGen Int
getStructOrder = FieldGen $ do
current <- get
let (result, _) = NE.head current
case current of
((x, y) :| rest) -> put ((x + 1, y) :| rest)
return result
getOrder :: FieldGen Int
getOrder = FieldGen $ do
current <- get
let (_, result) = NE.head current
case current of
((x, y) :| rest) -> put ((x + 1, y + 1) :| rest)
return result
-- Field types
-- need to enumerate each field; this will be performed during struct
-- generation where the number of fields is known (numberDefs)
genFieldDef :: [FieldType] -> FieldGen Field
genFieldDef structTypes = do
order <- getOrder
fieldName <- do
str <- liftGen $ QC.listOf1 genSafeLCChar
return $ FieldName (str ++ show order)
fieldType <- liftGen $ QC.elements (map BasicType [Bool ..] ++ structTypes)
return $ FieldDef fieldName order fieldType
-- Struct type
-- like fields, we enumerate each struct during generation for uniqueness
genStructDef :: Int -> FieldGen Field
genStructDef depth = do
order <- getStructOrder
pushFieldGen
-- generate the struct's name
structName <- do
fc <- liftGen genSafeUCChar
rest <- liftGen (QC.listOf genSafeLCChar)
return $ StructName ((fc : rest) ++ show order)
-- generate the nested structs
structNum <-
if depth <= 0
then pure 0
else liftGen (QC.choose (0, 3))
structDefs <- replicateM structNum (genStructDef (depth - 1))
-- extract the available struct types
let structTypes = map (\(StructDef sn _) -> StructType sn) structDefs
-- generate the fields using available struct types
fieldNum <- liftGen (QC.sized (\n -> QC.choose (1, 1 `max` n)))
fieldDefs <- replicateM fieldNum (genFieldDef structTypes)
popFieldGen
return $ StructDef structName (fieldDefs ++ structDefs)
-- Schema type
genSchema :: QC.Gen Schema
genSchema = do
id1st <- QC.elements ['a' .. 'f']
idrest <- QC.vectorOf 15 genSafeHexChar
-- multiple structs make tests take too long
content <- runFieldGen (genStructDef 3)
return $ Schema (id1st : idrest) [content]