packages feed

hic-0.0.0.1: src/Language/Cimple/Analysis/Pretty.hs

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE KindSignatures    #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms   #-}
module Language.Cimple.Analysis.Pretty
    ( ppErrorInfo
    , ppTypeError
    , ppContext
    , ppReason
    , ppProvenance
    , explainType
    , ppType
    , ppStdType
    , ppTypeDescr
    , showType
    , renderPlain
    , dedupDocs
    ) where

import           Control.Monad.State.Strict          (State, evalState)
import qualified Control.Monad.State.Strict          as State
import           Data.Fix                            (Fix (..), foldFix)
import qualified Data.Graph                          as Graph
import           Data.Map.Strict                     (Map)
import qualified Data.Map.Strict                     as Map
import           Data.Maybe                          (mapMaybe)
import           Data.Set                            (Set)
import qualified Data.Set                            as Set
import           Data.Text                           (Text)
import qualified Data.Text                           as T
import qualified Data.Text.IO                        as T
import qualified Data.Tree                           as Tree
import           Language.Cimple                     (AlexPosn (..),
                                                      Lexeme (..), sloc)
import qualified Language.Cimple                     as C
import           Language.Cimple.Analysis.Errors
import           Language.Cimple.Analysis.TypeSystem (pattern Array,
                                                      pattern BuiltinType,
                                                      pattern Const,
                                                      pattern EnumMem,
                                                      pattern ExternalType,
                                                      FullTemplate,
                                                      pattern FullTemplate,
                                                      FullTemplateF (..),
                                                      pattern Function,
                                                      pattern IntLit,
                                                      pattern NameLit,
                                                      pattern Nonnull,
                                                      pattern Nullable,
                                                      pattern Owner,
                                                      pattern Pointer,
                                                      pattern Singleton,
                                                      pattern Sized,
                                                      StdType (..),
                                                      pattern Template,
                                                      TemplateId (..),
                                                      TypeDescr (..), TypeInfo,
                                                      TypeInfoF (..),
                                                      TypeRef (..),
                                                      pattern TypeRef,
                                                      pattern Unsupported,
                                                      pattern Var,
                                                      pattern VarArg)
import qualified Language.Cimple.Analysis.TypeSystem as TypeSystem
import           Prettyprinter
import           Prettyprinter.Render.Terminal       (AnsiStyle, Color (..),
                                                      bold, color, renderStrict)

ppLexeme :: Pretty a => C.Lexeme a -> Doc AnsiStyle
ppLexeme = pretty . C.lexemeText

keywordStyle :: Doc AnsiStyle -> Doc AnsiStyle
keywordStyle = annotate (color Magenta)

typeStyle :: Doc AnsiStyle -> Doc AnsiStyle
typeStyle = annotate (color Cyan)

varStyle :: Doc AnsiStyle -> Doc AnsiStyle
varStyle = annotate (color Yellow)

literalStyle :: Doc AnsiStyle -> Doc AnsiStyle
literalStyle = annotate (color Blue)

errorStyle :: Doc AnsiStyle -> Doc AnsiStyle
errorStyle = annotate (color Red <> bold)

locationStyle :: Doc AnsiStyle -> Doc AnsiStyle
locationStyle = annotate (color White <> bold)

ppErrorInfo :: FilePath -> ErrorInfo p -> Maybe Text -> Doc AnsiStyle
ppErrorInfo path ei mSnippet =
    let locStr = case errLoc ei of
                    Just l  -> locationStyle (pretty (sloc path l) <> ":") <> " "
                    Nothing -> locationStyle (pretty path <> ":") <> " "
        interestingCtxs = dedupUnifications $ filterInteresting (errContext ei)
        ctxDocs = map ppContext interestingCtxs
        ctxPart = if null ctxDocs
                 then mempty
                 else line <> indent 2 (vsep ctxDocs)
        errPart = errorStyle (ppTypeError (errType ei))
        explPart = if null (errExplanation ei)
                  then mempty
                  else line <> indent 2 ("where" <+> align (vsep (errExplanation ei)))
        snippetPart = case (mSnippet, errLoc ei) of
            (Just snippet, Just (L (AlexPn _ _ col) _ _)) ->
                let caret = replicate (col - 1) ' ' ++ "^"
                in line <> line <> indent 2 (pretty snippet <> line <> errorStyle (pretty caret))
            _ -> mempty
    in locStr <> errPart <> ctxPart <> explPart <> snippetPart

dedupUnifications :: [Context p] -> [Context p]
dedupUnifications = \case
    [] -> []
    (c1@(InUnification e1 a1 _) : c2@(InUnification e2 a2 _) : rest)
        | e1 == e2 && a1 == a2 -> dedupUnifications (c1:rest)
        | otherwise -> c1 : dedupUnifications (c2:rest)
    (c:cs) -> c : dedupUnifications cs

filterInteresting :: [Context p] -> [Context p]
filterInteresting [] = []
filterInteresting (c:cs) =
    let cs' = filterInteresting cs
    in if isBoring c && any (not . isBoring) cs'
       then cs'
       else c : cs'

isBoring :: Context p -> Bool
isBoring = \case
    InExpr _ -> True
    InStmt _ -> True
    InUnification {} -> False
    _        -> False

ppContext :: Context p -> Doc AnsiStyle
ppContext = \case
    InFile _ -> mempty
    InFunction n -> "in function" <+> squotes (varStyle (pretty n))
    InMacro n -> "in macro" <+> squotes (varStyle (pretty n))
    InMemberAccess n -> "in member access" <+> squotes (varStyle (pretty n))
    InExpr (Fix node) -> "in expression:" <+> ppNodeSnippet node
    InStmt (Fix (C.Return _)) -> "in return statement"
    InStmt (Fix (C.IfStmt _ _ _)) -> "in if statement"
    InStmt (Fix (C.WhileStmt _ _)) -> "in while loop"
    InStmt (Fix (C.ForStmt _ _ _ _)) -> "in for loop"
    InStmt (Fix (C.VarDeclStmt _ _)) -> "in variable declaration"
    InStmt (Fix node) -> "in statement:" <+> ppNodeSnippet node
    InInitializer _ -> "in initializer"
    InUnification e a reason -> "while unifying" <+> ppType e <+> "and" <+> ppType a <+> parens (ppReason reason)

ppReason :: MismatchReason -> Doc AnsiStyle
ppReason = \case
    GeneralMismatch -> "general mismatch"
    ReturnMismatch -> "return type"
    ArgumentMismatch i -> "argument" <+> pretty i
    AssignmentMismatch -> "assignment"
    InitializerMismatch -> "initializer"

ppNodeSnippet :: C.NodeF (Lexeme Text) a -> Doc AnsiStyle
ppNodeSnippet = \case
    C.VarExpr (L _ _ n) -> varStyle (pretty n)
    C.LiteralExpr _ (L _ _ n) -> literalStyle (pretty n)
    C.BinaryExpr _ op _ -> "binary" <+> pretty (show op)
    C.UnaryExpr op _ -> "unary" <+> pretty (show op)
    C.FunctionCall _ _ -> "function call"
    C.AssignExpr _ _ _ -> "assignment"
    C.Return _ -> "return"
    C.IfStmt {} -> "if"
    C.WhileStmt {} -> "while"
    C.ForStmt {} -> "for"
    C.MemberAccess _ (L _ _ n) -> "." <> varStyle (pretty n)
    C.PointerAccess _ (L _ _ n) -> "->" <> varStyle (pretty n)
    _ -> "..."

ppTypeError :: TypeError p -> Doc AnsiStyle
ppTypeError = \case
    TypeMismatch exp' act reason mDetail ->
        let reasonDoc = case reason of
                            GeneralMismatch -> "type mismatch"
                            ReturnMismatch -> "return type mismatch"
                            ArgumentMismatch i -> "argument" <+> literalStyle (pretty i) <+> "type mismatch"
                            AssignmentMismatch -> "assignment type mismatch"
                            InitializerMismatch -> "initializer type mismatch"
            baseErr = reasonDoc <> ":" <+> "expected" <+> ppType exp' <> "," <+> "got" <+> ppType act
        in case mDetail of
            Just detail -> baseErr <> line <> indent 2 (ppMismatchDetail detail)
            Nothing -> baseErr
    UndefinedVariable n -> "undefined variable:" <+> varStyle (pretty n)
    UndefinedType n -> "undefined type:" <+> typeStyle (pretty n)
    MemberNotFound f ty -> "member" <+> varStyle (pretty f) <+> "not found in type" <+> ppType ty
    NotAStruct ty -> "not a struct or union:" <+> ppType ty
    TooManyArgs exp' act -> "too many arguments in function call: expected" <+> literalStyle (pretty exp') <> "," <+> "got" <+> literalStyle (pretty act)
    TooFewArgs exp' act -> "too few arguments in function call: expected" <+> literalStyle (pretty exp') <> "," <+> "got" <+> literalStyle (pretty act)
    NotALValue -> "assignment to non-lvalue"
    CallingNonFunction n ty -> "calling non-function type:" <+> varStyle (pretty n) <+> parens ("type:" <+> ppType ty)
    SwitchConditionNotIntegral ty -> "switch condition must be an integral type or enum (got" <+> ppType ty <> ")"
    DereferencingNonPointer ty -> "dereferencing non-pointer type:" <+> ppType ty
    ArrayAccessNonArray ty -> "array access on non-array type:" <+> ppType ty
    MacroArgumentMismatch n exp' act -> "macro" <+> varStyle (pretty n) <+> "expected" <+> literalStyle (pretty exp') <+> "arguments, got" <+> literalStyle (pretty act)
    MissingReturnValue ty -> "return statement with no value in function returning" <+> ppType ty
    InfiniteType n ty -> "infinite type detected: template" <+> typeStyle (pretty n) <+> "occurs in" <+> ppType ty
    CustomError msg -> pretty msg

ppMismatchDetail :: MismatchDetail p -> Doc AnsiStyle
ppMismatchDetail = \case
    MismatchDetail e a _ Nothing ->
        "types" <+> ppType e <+> "and" <+> ppType a <+> "are incompatible"
    MismatchDetail e a _ (Just (ctx, inner)) ->
        "while checking" <+> ppMismatchContext ctx <+> "of" <+> ppType e <+> "and" <+> ppType a <> ":" <> line <> indent 2 (ppMismatchDetail inner)
    MissingQualifier q _ _ ->
        "actual type is missing" <+> ppQualifier q <+> "qualifier"
    UnexpectedQualifier q _ _ ->
        "actual type has unexpected" <+> ppQualifier q <+> "qualifier"
    BaseMismatch e a ->
        "expected" <+> ppType e <> "," <+> "but got" <+> ppType a
    ArityMismatch e a ->
        "expected" <+> literalStyle (pretty e) <+> "arguments, but got" <+> literalStyle (pretty a)

ppMismatchContext :: MismatchContext -> Doc AnsiStyle
ppMismatchContext = \case
    InPointer -> "pointer target"
    InArray -> "array element"
    InFunctionReturn -> "return type"
    InFunctionParam i -> "parameter" <+> literalStyle (pretty i)

ppQualifier :: Qualifier -> Doc AnsiStyle
ppQualifier = keywordStyle . \case
    QOwner -> "owner"
    QNonnull -> "nonnull"
    QNullable -> "nullable"
    QConst -> "const"

ppProvenance :: Provenance p -> Doc AnsiStyle
ppProvenance = \case
    FromDefinition n (Just (L _ _ _)) -> parens ("definition of" <+> varStyle (pretty n))
    FromDefinition n Nothing -> parens ("definition of" <+> varStyle (pretty n))
    FromContext info -> "due to" <+> ppTypeError (errType info)
    FromInference _ -> parens "inferred from context"
    Builtin -> parens "builtin"

-- | Trace the origin of a type to provide a "Chain of Logic"
explainType :: Map (FullTemplate p) (TypeInfo p, Provenance p) -> TypeInfo p -> [Doc AnsiStyle]
explainType bindings ty =
    let initialKeys = TypeSystem.collectUniqueTemplateVars [ty]
        allTargetTemplates = concatMap (TypeSystem.collectUniqueTemplateVars . return . fst) (Map.elems bindings)
        allKeys = Set.fromList (initialKeys ++ Map.keys bindings ++ allTargetTemplates)

        mkNode key =
            let (tid, mIdx) = (ftId key, ftIndex key) in
            case Map.lookup key bindings of
                Just (target, prov) ->
                    let doc = "template" <+> typeStyle (ppType (Template tid mIdx)) <+> "was bound to" <+> ppType target <+> ppProvenance prov
                        deps = TypeSystem.collectUniqueTemplateVars [target]
                    in (doc, key, deps)
                Nothing ->
                    let doc = case mIdx of
                                Just idx -> "template" <+> pretty (TypeSystem.templateIdToText tid) <+> "indexed by" <+> pretty (showType idx)
                                Nothing  -> "template" <+> typeStyle (pretty (TypeSystem.templateIdToText tid)) <+> "is unbound"
                    in (doc, key, [])

        nodes = map mkNode (Set.toList allKeys)
        (graph, nodeFromVertex, vertexFromKey) = Graph.graphFromEdges nodes

        startVertices = mapMaybe vertexFromKey initialKeys
        forest = Graph.dfs graph startVertices
    in concatMap (Tree.flatten . fmap ((\(d, _, _) -> d) . nodeFromVertex)) forest


ppStdType :: StdType -> Doc AnsiStyle
ppStdType = typeStyle . \case
    VoidTy    -> "void"
    BoolTy    -> "bool"
    CharTy    -> "char"
    U08Ty     -> "uint8_t"
    S08Ty     -> "int8_t"
    U16Ty     -> "uint16_t"
    S16Ty     -> "int16_t"
    U32Ty     -> "uint32_t"
    S32Ty     -> "int32_t"
    U64Ty     -> "uint64_t"
    S64Ty     -> "int64_t"
    SizeTy    -> "size_t"
    F32Ty     -> "float"
    F64Ty     -> "double"
    NullPtrTy -> "nullptr_t"

ppType :: TypeInfo p -> Doc AnsiStyle
ppType = foldFix $ \case
    TypeRefF ref l args ->
        let prefix = keywordStyle $ case ref of
                StructRef -> "struct "
                UnionRef  -> "union "
                EnumRef   -> "enum "
                _         -> ""
        in prefix <> typeStyle (ppLexeme l) <> if null args then mempty else angles (hsep $ punctuate comma args)
    PointerF t -> t <> "*"
    SizedF t l -> t <> brackets (varStyle (ppLexeme l))
    QualifiedF qs t ->
        t <+> hsep (map ppQualifier (Set.toList qs))
    BuiltinTypeF std -> ppStdType std
    ExternalTypeF l -> typeStyle (ppLexeme l)
    ArrayF mt dims ->
        maybe (typeStyle "void") id mt <> hcat (map brackets dims)
    VarF l t -> t <+> varStyle (ppLexeme l)
    FunctionF ret params ->
        ret <> parens (hsep $ punctuate comma params)
    TemplateF (FullTemplate t i) -> typeStyle (pretty t) <> maybe mempty brackets i
    SingletonF std v -> ppStdType std <> "=" <> literalStyle (pretty v)
    VarArgF -> "..."
    IntLitF l -> literalStyle (ppLexeme l)
    NameLitF l -> varStyle (ppLexeme l)
    EnumMemF l -> varStyle (ppLexeme l)
    UnconstrainedF -> errorStyle "unconstrained"
    ConflictF -> errorStyle "conflict"
    ProxyF t -> t
    UnsupportedF msg -> errorStyle "unsupported" <> parens (pretty msg)

ppTypeDescr :: TypeDescr p -> Doc AnsiStyle
ppTypeDescr = \case
    StructDescr l _ mems -> keywordStyle "struct" <+> typeStyle (ppLexeme l) <+> lbrace <> line <> indent 2 (vsep (map ppMember mems)) <> line <> rbrace
    UnionDescr l _ mems  -> keywordStyle "union" <+> typeStyle (ppLexeme l) <+> lbrace <> line <> indent 2 (vsep (map ppMember mems)) <> line <> rbrace
    EnumDescr l _        -> keywordStyle "enum" <+> typeStyle (ppLexeme l)
    IntDescr l std       -> keywordStyle "typedef" <+> ppStdType std <+> typeStyle (ppLexeme l)
    FuncDescr l _ ret ps -> keywordStyle "typedef" <+> ppType ret <+> typeStyle (ppLexeme l) <> parens (hsep $ punctuate comma $ map ppType ps)
    AliasDescr l _ t     -> keywordStyle "typedef" <+> ppType t <+> typeStyle (ppLexeme l)
  where
    ppMember (l, t) = ppType t <+> varStyle (ppLexeme l) <> semi

showType :: TypeInfo p -> Text
showType = renderPlain . ppType

renderPlain :: Doc AnsiStyle -> Text
renderPlain = renderStrict . layoutPretty defaultLayoutOptions . unAnnotate

dedupDocs :: [Doc AnsiStyle] -> [Doc AnsiStyle]
dedupDocs = go Set.empty
  where
    go _ [] = []
    go seen (d:ds) =
        let rendered = renderPlain d
        in if Set.member rendered seen
           then go seen ds
           else d : go (Set.insert rendered seen) ds