packages feed

hic-0.0.0.1: test/Language/Cimple/Hic/InferenceSpec.hs

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
module Language.Cimple.Hic.InferenceSpec
    ( checkInference
    , checkRefactoring
    , mustParse
    , mustParseNodes
    , spec
    ) where

import           Data.Fix                      (Fix (..))
import           Data.Map.Strict               (Map)
import qualified Data.Map.Strict               as Map
import           Data.Maybe                    (fromMaybe)
import           Data.Text                     (Text)
import qualified Data.Text                     as T
import           GHC.Stack                     (HasCallStack)
import           Test.Hspec                    (Spec, describe, it, shouldBe,
                                                shouldContain)

import qualified Language.Cimple               as C
import           Language.Cimple.Hic           (lower)
import           Language.Cimple.Hic.Ast       as H
import           Language.Cimple.Hic.Inference (inferProgram)
import           Language.Cimple.Hic.Pretty    (showNodePlain)
import qualified Language.Cimple.IO            as C
import qualified Language.Cimple.Program       as C

mustParse :: (HasCallStack, MonadFail m) => [Text] -> m (C.Program Text)
mustParse code =
    case C.parseText $ T.unlines code of
        Left err -> fail err
        Right nodes -> case C.fromList [("test.c", nodes)] of
            Left err -> fail err
            Right ok -> return ok

mustParseNodes :: (HasCallStack, MonadFail m) => [Text] -> m [C.Node (C.Lexeme Text)]
mustParseNodes code = do
    prog <- mustParse code
    case lookup "test.c" (C.toList prog) of
        Just nodes -> return nodes
        Nothing    -> fail "expected test.c in program"

checkInference :: HasCallStack => [Text] -> [Text] -> Spec
checkInference input expectedHic = checkRefactoring input expectedHic input

checkRefactoring :: HasCallStack => [Text] -> [Text] -> [Text] -> Spec
checkRefactoring input expectedHic expectedRefactored =
    let desc = case input of
            (x:_) -> T.unpack x
            []    -> "empty input"
    in it desc $ do
    prog <- mustParse input
    let (hicAsts, diags) = inferProgram prog
    diags `shouldBe` []
    hicNodes <- case Map.lookup "test.c" hicAsts of
        Just nodes | not (null nodes) -> return nodes
        _                             -> fail "expected at least one hic node"

    -- Check pretty-printed Hic
    let printed = T.intercalate "\n" $ map showNodePlain hicNodes
    printed `shouldBe` T.intercalate "\n" expectedHic

    -- Check reversibility against refactored version
    let lowered = map lower hicNodes
    refactoredNodes <- mustParseNodes expectedRefactored
    map (C.removeSloc . C.elideGroups) lowered `shouldBe` map (C.removeSloc . C.elideGroups) refactoredNodes

spec :: Spec
spec = describe "Global Inference" $ do
    it "combines multiple inference features" $ do
        prog <- mustParse
            [ "typedef enum Tox_Event_Type { TOX_EVENT_FRIEND_MESSAGE } Tox_Event_Type;"
            , "typedef union Tox_Event_Data { struct Tox_Event_Friend_Message *friend_message; } Tox_Event_Data;"
            , "struct Tox_Event { Tox_Event_Type type; Tox_Event_Data data; };"
            , "void handle_events(struct Tox_Event *events, int count) {"
            , "    for (int i = 0; i < count; ++i) {"
            , "        switch (events[i].type) {"
            , "            case TOX_EVENT_FRIEND_MESSAGE: {"
            , "                handle_message(events[i].data.friend_message);"
            , "                break;"
            , "            }"
            , "        }"
            , "    }"
            , "}"
            ]
        let (hicAsts, diags) = inferProgram prog
        diags `shouldBe` []
        hicNodes <- case Map.lookup "test.c" hicAsts of
            Just nodes -> return nodes
            _          -> fail "expected test.c in program"

        let printed = T.unpack $ T.intercalate "\n" $ map showNodePlain hicNodes
        -- We expect a ForEach containing a Match
        printed `shouldContain` "for_each i in events"
        printed `shouldContain` "match i {"
        printed `shouldContain` "TOX_EVENT_FRIEND_MESSAGE => {"
        printed `shouldContain` "handle_message(i.friend_message);"

-- end of tests