packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Backend/HsModule/Pretty/Decl.hs

{-# LANGUAGE RecordWildCards #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

module HsBindgen.Backend.HsModule.Pretty.Decl (

  ) where

import Data.Text qualified as Text
import Text.SimplePrettyPrint (CtxDoc, Pretty (..), ($$), (<+>), (><))
import Text.SimplePrettyPrint qualified as PP

import HsBindgen.Backend.Global
import HsBindgen.Backend.Hs.AST qualified as Hs
import HsBindgen.Backend.Hs.CallConv
import HsBindgen.Backend.Hs.Haddock.Documentation qualified as HsDoc
import HsBindgen.Backend.Hs.Name qualified as Hs
import HsBindgen.Backend.HsModule.Names
import HsBindgen.Backend.HsModule.Pretty.Comment
import HsBindgen.Backend.HsModule.Pretty.Expr ()
import HsBindgen.Backend.HsModule.Pretty.Type
import HsBindgen.Backend.SHs.AST
import HsBindgen.Instances as Inst
import HsBindgen.IR.C qualified as C

instance Pretty SDecl where
  pretty = \case
    DTypSyn typSyn ->
         prettyTopLevelComment typSyn.comment
      $$ PP.fsep [
             "type" <+> pretty typSyn.name <+> PP.char '='
           , PP.nest 2 (pretty typSyn.typ)
           ]
    DInst inst ->
      let constraints = map pretty inst.super
          -- @flist@ should either be @PP.hlist@ or @PP.vlist@
          clsContext flist = flist "(" ")" constraints
          clsHead = PP.hsep (pretty (resolveTypeClass inst.clss) : map (prettyPrec appPrec1) inst.args)
          cls flist =
                "instance"
            <+> (if null inst.super
                  then PP.empty
                  else clsContext flist <+> "=>")
            <+> clsHead
            <+> "where"

          instanceHead = PP.ifFits (cls PP.hlist) (cls PP.hlist) (cls PP.vlist)
          typs = flip map inst.types $ \(g, typArgs, typSyn) -> PP.nest 2 $ PP.fsep
            [ "type" <+> prettyUnqualResolvedName (resolveGlobal g) <+> PP.hsep (map (prettyPrec appPrec1) typArgs)
                <+> PP.char '='
            , PP.nest 2 (pretty typSyn)
            ]
          decs = flip map inst.decs $ \(name, expr) -> PP.nest 2 $ PP.fsep
            [ prettyUnqualResolvedName (resolveGlobal name) <+> PP.char '='
            , PP.nest 2 (pretty expr)
            ]

      in   prettyTopLevelComment inst.comment PP.$$
           PP.vsep (instanceHead : typs ++ decs)

    DRecord record ->
      let d = PP.hsep ["data", pretty record.typ, PP.char '=', pretty record.con]
      in  prettyTopLevelComment record.comment
       $$ ( PP.hang d 2 $ PP.vcat [
                PP.vlist "{" "}" [
                       PP.hsep [
                           pretty field.name
                         , "::"
                         , pretty field.typ
                         ]
                    $$ prettyFieldComment
                  | field <- record.fields
                  , let prettyFieldComment = maybe PP.empty (pretty . PartOfDeclarationComment) field.comment
                  ]
              , nestedDeriving record.deriv
              ]
          )

    DEmptyData empty ->
      prettyTopLevelComment empty.comment
        $$ PP.hsep ["data", pretty empty.name]

    DNewtype newtyp ->
      let d = PP.hsep ["newtype", pretty newtyp.name, PP.char '=', pretty newtyp.con]
          prettyFieldComment = maybe PP.empty (pretty . PartOfDeclarationComment) newtyp.field.comment
      in  prettyTopLevelComment newtyp.comment
       $$ ( PP.hang d 2 $ PP.vcat [
                PP.vlist "{" "}" [
                      PP.hsep [
                          pretty newtyp.field.name
                        , "::"
                        , pretty newtyp.field.typ
                        ]
                    $$ prettyFieldComment
                  ]
              , nestedDeriving newtyp.deriv
              ]
          )

    DForeignImport foreignImport ->
      -- Variable names here refer to the syntax of foreign declarations at
      -- <https://www.haskell.org/onlinereport/haskell2010/haskellch8.html#x15-1540008.4>
      let callconv, impent :: CtxDoc
          (callconv, impent) =
            case foreignImport.callConv of
              CallConvUserlandCapi _ -> ("ccall",
                  PP.string $ Text.unpack foreignImport.origName.text
                )
              CallConvGhcCapi header -> ("capi", PP.hcat [
                  PP.string header
                , PP.string $ Text.unpack foreignImport.origName.text
                ])
              CallConvGhcCCall style -> ("ccall", PP.hcat [
                  case style of
                    ImportAsValue -> ""
                    ImportAsPtr   -> "&"
                , PP.string $ Text.unpack foreignImport.origName.text
                ])

      in  prettyTopLevelComment foreignImport.comment
       $$ PP.hsep [ "foreign import"
               , callconv
               , safety foreignImport.safety
               , "\"" >< impent >< "\""
               , pretty foreignImport.name
               , "::"
               ]
       $$ PP.nest 5 (prettyBindingType foreignImport.parameters foreignImport.result)

    DBinding Binding{..} ->
      let prettyName    = pretty name
          prettyTyp     = prettyBindingType parameters result
          prettySignature =
            if null parameters; then
              prettyName <+> "::" <+> prettyTyp
            else
              prettyName <+> "::" $$  PP.nest 5 prettyTyp
      in  PP.vcat (map (prettyPragma name) pragmas)
       $$ prettyTopLevelComment comment
       $$ prettySignature
       $$ PP.fsep
            [ prettyName <+> PP.char '='
            , PP.nest 2 $ pretty body
            ]

    DDerivingInstance deriv ->
      prettyTopLevelComment deriv.comment
        $$ "deriving" <+> strategy deriv.strategy
                      <+> "instance"
                      <+> pretty deriv.typ

    DPatternSynonym patSyn ->
      PP.vcat [
          prettyTopLevelComment patSyn.comment
        , "pattern" <+> pretty patSyn.name <+> "::" <+> pretty patSyn.typ
        , "pattern" <+> pretty patSyn.name <+> "=" <+> pretty patSyn.rhs
        ]

    DCompletePragma completePragma ->
      let patterns = map pretty completePragma.patterns
          oneLine  = PP.hlist "{-# COMPLETE " " #-}" patterns
      in  PP.ifFits oneLine oneLine $
            PP.vcat . ("{-# COMPLETE" :) . (++ [PP.nest 2 "#-}"]) $
              [ if idx == (0 :: Int)
                  then PP.nest 6 pat
                  else PP.nest 4 ("," <+> pat)
              | (idx, pat) <- zip [0..] patterns
              ]

{-------------------------------------------------------------------------------
  PatEpxr pretty-printing
-------------------------------------------------------------------------------}

instance Pretty PatExpr where
  prettyPrec = prettyPatExpr

prettyPatExpr :: Int -> PatExpr -> CtxDoc
prettyPatExpr prec = \case
    PELit i -> PP.parensWhen (i < 0) $ PP.show i
    PEApps n ps -> PP.parensWhen (prec > 3) $ pretty n <+> PP.hsep (map (prettyPatExpr 4) ps)

{-------------------------------------------------------------------------------
  Helpers
-------------------------------------------------------------------------------}

-- | Nested deriving clauses (as part of a datatype declaration)
nestedDeriving :: [(Hs.Strategy ClosedType, [Inst.TypeClass])] -> CtxDoc
nestedDeriving = PP.vcat . map (uncurry aux)
  where
    aux :: Hs.Strategy ClosedType -> [Inst.TypeClass] -> CtxDoc
    aux strat insts =
      let l = auxOneLine strat insts
      in  PP.ifFits l l $ auxMultiLines strat insts

    auxOneLine :: Hs.Strategy ClosedType -> [Inst.TypeClass] -> CtxDoc
    auxOneLine strat insts = PP.hsep [
        "deriving"
      , strategy strat
      , PP.hlist "(" ")" (map (pretty . resolveTypeClass) insts)
      ]

    auxMultiLines :: Hs.Strategy ClosedType -> [Inst.TypeClass] -> CtxDoc
    auxMultiLines strat insts = PP.hang ("deriving" <+> strategy strat) 2 $
      PP.vlist "(" ")" (map (pretty . resolveTypeClass) insts)

strategy :: Hs.Strategy ClosedType -> CtxDoc
strategy Hs.DeriveNewtype  = "newtype"
strategy Hs.DeriveStock    = "stock"
strategy (Hs.DeriveVia ty) = "via" <+> pretty ty

prettyPragma :: Hs.TermName -> Pragma -> CtxDoc
prettyPragma n = \case
  NOINLINE -> "{-# NOINLINE" <+> pretty n <+> "#-}"

safety :: Safety -> CtxDoc
safety Safe = "safe"
safety Unsafe = "unsafe"

prettyBindingType :: [Parameter] -> Result -> CtxDoc
prettyBindingType params result =
  case params of
    [] -> prettyResultType result.typ
    _  -> prettyParams params
  where
    prettyParam p =
         prettyPrec funPrec1 p.typ
      $$ maybe PP.empty (pretty . PartOfDeclarationComment) p.comment


    prettyResultType t = prettyPrec funPrec t

    prettyParams []     = prettyResultType result.typ
    prettyParams (p:ps) =
         prettyParam p
      $$ PP.nest (-3) ("->" <+> prettyParams ps)

prettyTopLevelComment :: Maybe HsDoc.Comment -> CtxDoc
prettyTopLevelComment = maybe PP.empty (pretty . TopLevelComment)


-- | Pretty-print a 'HsBindgen.Backend.HsModule.Names.ResolvedName' unqualified
--
-- This is needed in instance declarations.
prettyUnqualResolvedName :: ResolvedName -> CtxDoc
prettyUnqualResolvedName resolved =
    PP.parensWhen (resolved.typ == OperatorName) $ PP.string resolved.string

resolveTypeClass :: Inst.TypeClass -> ResolvedName
resolveTypeClass = resolveGlobal . typeClassGlobal