libclang-bindings-0.1.0.0: test/Test/Util/Input/StructForest.hs
-- | Randomly generated C input: tree of structs
--
-- Intended for qualified import.
--
-- > import Test.Util.Input.StructForest (StructForest)
-- > import Test.Util.Input.StructForest qualified as StructForest
module Test.Util.Input.StructForest (
-- * Definition
StructForest(StructForest, structForest)
, StructTree(..)
, StructField(..)
-- * Query
, lookup
-- * Execution
, toAbstractAST
, toTestInput
) where
import Prelude hiding (lookup)
import Data.Foldable (asum)
import Data.List qualified as List
import Data.String
import Data.Tree (Tree (Node))
import Test.QuickCheck
import Test.Util.AST (AST (..))
import Test.Util.AST qualified as AST
import Test.Util.Input (TestInput (..))
import Test.Util.Input qualified as Input
import Test.Util.Shape (Shape)
import Test.Util.Shape qualified as Shape
import Clang.LowLevel.Core (CXCursorKind (..))
{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}
-- | List of struct trees
--
-- The annotation type (@a@) is used for 'FoldException.Info' or @()@.
data StructForest a =
StructForest {
structForest :: [StructTree a]
, structForestShape :: Shape a
}
deriving stock (Show)
-- | Tree of structs
data StructTree a =
StructTree {
structName :: String
, structFields :: [StructField a]
, structAnn :: a
}
deriving stock (Show)
data StructField a =
StructField {
fieldName :: String
, fieldType :: FieldType a
}
deriving stock (Show)
data FieldType a =
TypeInt
| TypeStruct (StructTree a)
deriving stock (Show)
{-------------------------------------------------------------------------------
Construction
-------------------------------------------------------------------------------}
instance Arbitrary a => Arbitrary (StructForest a) where
arbitrary = fromShape <$> arbitrary1
shrink = map fromShape . shrink1 . structForestShape
fromShape :: forall a. Shape a -> StructForest a
fromShape structForestShape = StructForest {
structForest = map struct $ Shape.toForest structForestShape
, structForestShape
}
where
struct :: Tree (a, [Int]) -> StructTree a
struct (Node (x, path) children) = StructTree {
structName = mkStructName path
, structFields = map field children
, structAnn = x
}
field :: Tree (a, [Int]) -> StructField a
field node@(Node (_x, path) children) = StructField {
fieldName = mkFieldName path
, fieldType = case children of
[] -> TypeInt
_ -> TypeStruct (struct node)
}
{-------------------------------------------------------------------------------
Query
-------------------------------------------------------------------------------}
-- | Get annotation of specified struct
lookup :: forall a. String -> StructForest a -> Maybe a
lookup key StructForest{structForest} =
asum $ map goStruct structForest
where
goStruct :: StructTree a -> Maybe a
goStruct StructTree{structName, structFields, structAnn}
| structName == key = Just structAnn
| otherwise = asum $ map goField structFields
goField :: StructField a -> Maybe a
goField StructField{fieldType} =
case fieldType of
TypeInt -> Nothing
TypeStruct struct -> goStruct struct
{-------------------------------------------------------------------------------
Expected AST
-------------------------------------------------------------------------------}
toAbstractAST :: forall a.
a -- ^ Annotation on fields
-> StructForest a -> AST (AST.Descr, a)
toAbstractAST fieldAnn =
AST . AST.Siblings . map goStruct . structForest
where
goStruct :: StructTree a -> AST.Node (AST.Descr, a)
goStruct StructTree{structName, structFields, structAnn} =
AST.Node (descr, structAnn) $
AST.Siblings (concatMap goField structFields)
where
descr :: AST.Descr
descr = AST.defaultDescr structName CXCursor_StructDecl
goField :: StructField a -> [AST.Node (AST.Descr, a)]
goField StructField{fieldName, fieldType} =
case fieldType of
TypeInt -> [
AST.Node (descr, fieldAnn) $ AST.Siblings []
]
-- The clang AST has a weird quirk, where the struct is repeated (or
-- at least visited) /twice/: once before the field, and once as a
-- child /of/ the field.
TypeStruct struct -> [
goStruct struct
, AST.Node (descr, fieldAnn) $ AST.Siblings [goStruct struct]
]
where
descr :: AST.Descr
descr = AST.defaultDescr fieldName CXCursor_FieldDecl
{-------------------------------------------------------------------------------
Generate test input
-------------------------------------------------------------------------------}
toTestInput :: forall a. AST.ShowComment a => StructForest a -> TestInput
toTestInput StructForest{structForest} = mconcat [
Input.unlines [
"#ifndef STRUCT_TREE"
, "#define STRUCT_TREE"
, ""
]
, Input.intercalate "\n\n" $ map (goStruct Nothing) structForest
, Input.unlines [
""
, "#endif // STRUCT_TREE"
]
]
where
goStruct :: Maybe String -> StructTree a -> TestInput
goStruct mFieldName StructTree{structName, structFields, structAnn} =
Input.intercalate "\n" . mconcat $ [
[ comment
| Just comment <- [AST.showComment structAnn]
]
, [ fromString $ "struct " ++ structName ++ " {"
, Input.indent $ Input.intercalate "\n" $ map goField structFields
, fromString $ "}" ++ maybe "" (" " ++) mFieldName ++ ";"
]
]
goField :: StructField a -> TestInput
goField StructField{fieldName, fieldType} =
case fieldType of
TypeInt -> fromString $ "int " ++ fieldName ++ ";"
TypeStruct struct -> goStruct (Just fieldName) struct
{-------------------------------------------------------------------------------
'IsConcrete'
-------------------------------------------------------------------------------}
instance AST.IsConcrete (StructForest ()) where
toAbstractAST = fmap fst . toAbstractAST ()
toTestInput = toTestInput
{-------------------------------------------------------------------------------
Internal auxiliary
-------------------------------------------------------------------------------}
renderPath :: [Int] -> String
renderPath = List.intercalate "_" . map show
mkStructName :: [Int] -> [Char]
mkStructName path = "s" ++ renderPath path
mkFieldName :: [Int] -> String
mkFieldName path = "f" ++ renderPath path