mirror of
https://github.com/zap-proto/haskell.git
synced 2026-10-30 15:05:00 +00:00
182 lines
4.4 KiB
Haskell
182 lines
4.4 KiB
Haskell
{-# 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]
|