packages feed

libclang-bindings-0.1.0.0: test/Test/Util/AST.hs

-- | Abstract syntax tree
--
-- Intended for qualified import.
--
-- > import Test.Util.AST (AST(..))
-- > import Test.Util.AST qualified as AST
module Test.Util.AST (
    -- * Definition
    AST(..)
  , Siblings(..)
  , Node(..)
  , Descr(..)
  , defaultDescr
    -- * Concrete ASTs
  , IsConcrete(..)
  , ShowComment(..)
    -- * Clang interop
  , descrAt
  , fold
  , parse
  , parseUsing
  ) where

import Data.Text qualified as Text
import Data.Tree (Forest, Tree)
import Data.Tree qualified as Tree
import Test.Util.Clang qualified as Clang
import Test.Util.Input (TestInput)

import Clang.Enum.Simple
import Clang.HighLevel.Types
import Clang.LowLevel.Core

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | Top-level AST
--
-- The most common instantiation of @a@ is 'Descr'.
data AST a = AST (Siblings a)
  deriving stock (Eq, Functor)

-- | Siblings (children of the same node)
data Siblings a = Siblings [Node a]
  deriving stock (Eq, Functor)

-- | Single node in the AST
data Node a = Node a (Siblings a)
  deriving stock (Eq, Functor)

-- | Description of a node in the tree
newtype Descr = Descr String
  deriving newtype (Eq, Show)

-- | Default description of a name and a kind
defaultDescr :: String -> CXCursorKind -> Descr
defaultDescr name kind = Descr $ name ++ " (" ++ show kind ++ ")"

{-------------------------------------------------------------------------------
  'Show' instances
-------------------------------------------------------------------------------}

-- | Produce human readable AST
--
-- This instance (as well as other 'Show' instances in the test infrastructure)
-- is not law-abiding, as it does not generate valid Haskell code. It is used by
-- QuickCheck for more readable output.
instance Show a => Show (AST a) where
  show = ("\n" ++) . Tree.drawTree . addTop . toForest . fmap show
    where
      -- An artificial @<top>@ node makes the output more readable
      -- (otherwise top-level siblings seem unrelated)
      addTop :: Forest String -> Tree String
      addTop = Tree.Node "<top>"

{-------------------------------------------------------------------------------
  Conversions
-------------------------------------------------------------------------------}

fromSiblings :: Siblings a -> [Node a]
fromSiblings (Siblings xs) = xs

-- | Internal: translate to 'Forest'
--
-- We don't use 'Forest' and 'Tree' directly because we want to use 'Siblings'
-- instead of @[]@ for improved type-level clarity.
toForest :: forall a. AST a -> Forest a
toForest = \(AST xs) -> goSiblings xs
  where
    goSiblings :: Siblings a -> Forest a
    goSiblings = map goNode . fromSiblings

    -- goNode :: Node a -> Tree a
    goNode :: Node a -> Tree a
    goNode (Node x xs) = Tree.Node x (goSiblings xs)

{-------------------------------------------------------------------------------
  Concrete ASTs
-------------------------------------------------------------------------------}

-- | Concrete AST
--
-- For something to be a concrete AST, we need to be able to produce a C
-- header ('TestInput') from it, such that when we parse that C input, the
-- resulting abstract AST is precisely the expected abstract AST.
class IsConcrete a where
  toTestInput   :: a -> TestInput
  toAbstractAST :: a -> AST Descr

-- | Show something as an optional C comment
--
-- This is sometimes helpful when implementing 'toTestInput'.
class ShowComment a where
  showComment :: a -> Maybe TestInput

instance ShowComment () where
  showComment _ = Nothing

{-------------------------------------------------------------------------------
  Construction
-------------------------------------------------------------------------------}

descrAt :: CXCursor -> IO Descr
descrAt curr = do
    mKind <- fromSimpleEnum <$> clang_getCursorKind curr
    name  <- Text.unpack <$> clang_getCursorSpelling curr
    case mKind of
      Right kind -> return $ defaultDescr name kind
      Left  err  -> error $ "Unknown kind:  " ++ show err

-- | Construct the AST
--
-- Since this is for testing purposes, we ignore most of the contents of the
-- AST, recording only its shape and names.
fold :: Fold IO (Node Descr)
fold = simpleFold $ \curr -> do
    node <- descrAt curr
    foldRecursePure fold (Node node . Siblings)

parseUsing :: Fold IO (Node Descr) -> TestInput -> IO (AST Descr)
parseUsing f input = AST . Siblings <$> Clang.parseUsing f input

parse :: TestInput -> IO (AST Descr)
parse = parseUsing fold