packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/IR/C/PrettyPrinter.hs

-- | Pretty-print valid C code
--
-- This module should only be used within the @HsBindgen.IR@ hierarchy.  From
-- outside the @HsBindgen.IR@ hierarchy, "HsBindgen.IR.C" should be used.
--
-- Within @HsBindgen.IR@, all modules aside from "HsBindgen.IR.C" should import
-- this module qualified for consistency.
--
-- > import HsBindgen.IR.C.PrettyPrinter qualified as C
module HsBindgen.IR.C.PrettyPrinter (
    -- * Pretty-printers
    showsFunctionType
  , showsVariableType
  , showsType
  , showsFunctionPurity
  ) where

import Data.Text qualified as Text

import HsBindgen.Errors
import HsBindgen.Imports
import HsBindgen.IR.C.Decl qualified as C
import HsBindgen.IR.C.Naming qualified as C
import HsBindgen.IR.C.Type qualified as C
import HsBindgen.IR.Pass
import HsBindgen.Language.C qualified as C

{-------------------------------------------------------------------------------
  Pretty-printers
-------------------------------------------------------------------------------}

-- | Formats functions with attributes on their own line, parameters on separate
-- lines with additional indentation.
--
showsFunctionType :: forall p.
     (IsPass p, HasCallStack)
  => ShowS               -- ^ function name
  -> C.FunctionPurity    -- ^ function purity
  -> [(ShowS, C.Type p)] -- ^ arguments, names and types
  -> C.Type p            -- ^ return type
  -> ShowS
showsFunctionType n pur args res  =
      showsFunctionPurity pur
    . showAttributeNewline pur
    . showsType functionDeclarator res
  where
    functionDeclarator ::
         CTypePrecedence
      -> ShowS
    functionDeclarator d = showParen (d > arrayPrec) $
        n . showChar ' ' . showParen True signatureArgs

    signatureArgs :: ShowS
    signatureArgs = case args of
        [] -> showString "void"
        p:ps ->
              showChar '\n'
            . foldr1 sep (fmap showT (p :| ps))
            . showChar '\n'
      where
        sep a b = a . showString ",\n" . b

        showT :: (ShowS, C.Type p) -> ShowS
        showT (i, p) =
              showString "  "  -- extra 2 spaces for parameters
            . showsVariableType i p

showsVariableType ::
     (IsPass p, HasCallStack)
  => ShowS -- ^ variable name
  -> C.Type p
  -> ShowS
showsVariableType n ty = showsType variableDeclarator ty
  where
    -- Just the identifier
    variableDeclarator _ = n

-- | Show type in C syntax.
-- Used to generate userland-capi C-code.
--
-- NOTE: it is not unlikely that this function places parentheses incorrectly in
-- edge cases.
--
-- <https://en.cppreference.com/w/c/language/declarations.html>
--
-- === Examples
--
-- >>> import HsBindgen.Frontend.AST.Decl (Type (..))
-- >>> import HsBindgen.Language.C qualified as C
-- >>> import HsBindgen.Frontend.AST.Decl (FunctionPurity (..))
--
-- A variable @a@ of type boolean:
--
-- >>> showsVariableType (showString "a") (TypePrim C.PrimBool) ""
-- "_Bool a"
--
-- A variable @b@ of type incomplete-array-of-integer:
--
-- >>> :{
--  showsVariableType
--    (showString "b")
--    (TypeIncompleteArray (TypePrim (C.PrimIntegral C.PrimInt C.Signed)))
--    ""
-- :}
-- "signed int b[]"
--
-- A variable @c@ of type pointer-to-3-length-array-of-integer:
--
-- >>> :{
--  showsVariableType
--    (showString "c")
--    (TypePointer (TypeConstArray 3 (TypePrim (C.PrimIntegral C.PrimInt C.Signed))))
--    ""
-- :}
-- "signed int (*c)[3]"
--
-- A variable @d@ of type 3-length-array-of-pointer-to-integer:
--
-- >>> :{
--  showsVariableType
--    (showString "d")
--    (TypeConstArray 3 (TypePointer (TypePrim (C.PrimIntegral C.PrimInt C.Signed))))
--    ""
-- :}
-- "signed int *d[3]"
--
-- A function @foo@ of return type pointer-to-integer:
--
-- >>> :{
--  showsFunctionType
--    (showString "foo")
--    ImpureFunction
--    []
--    (TypePointer (TypePrim (C.PrimIntegral C.PrimInt C.Signed)))
--    ""
-- :}
-- "signed int *foo (void)"
--
-- A function @bar@ of return type pointer-to-2x3-size-array-of-integer:
--
-- >>> :{
--  showsFunctionType
--    (showString "bar")
--    ImpureFunction
--    [(showString "arg1", TypePrim (C.PrimIntegral C.PrimInt C.Signed))]
--    (TypePointer (TypeConstArray 2 (TypeConstArray 3 (TypePrim (C.PrimIntegral C.PrimInt C.Signed)))))
--    ""
-- :}
-- "signed int (*bar (signed int arg1))[2][3]"
showsType :: forall p.
     (IsPass p, HasCallStack)
  => (CTypePrecedence -> ShowS)  -- ^ variable name, or function name + arguments
  -> C.Type p
  -> ShowS
showsType x (C.TypePrim p)            = C.showsPrimType p . showChar ' ' . x 0
showsType x (C.TypeRef ref)           = showsId (Proxy @p) ref . showChar ' ' . x 0
showsType x (C.TypeEnum ref)          = showsId (Proxy @p) ref.name . showChar ' ' . x 0
showsType x (C.TypeMacro ref)         = showsId (Proxy @p) (macroIdId (Proxy @p) ref.name) . showChar ' ' . x 0
showsType x (C.TypeTypedef ref)       = showsId (Proxy @p) ref.name . showChar ' ' . x 0
showsType x (C.TypePointers n t)      = showsType (\d -> showParen (d > arrayPrec)
                                      $ foldr (.) id (replicate n (showString "*"))
                                      . x (pointerPrec + 1)) t
showsType x (C.TypeConstArray n t)    = showsType (\_d -> x (arrayPrec + 1) . showChar '[' . shows n . showChar ']') t
showsType x (C.TypeIncompleteArray t) = showsType (\_d -> x (arrayPrec + 1) . showString "[]") t
showsType x (C.TypeFun args res)      =
    -- Note: we pass 'ImpureFunction' to 'showsFunctionType' so that no function
    -- attributes are included in the printed string. Function attributes should
    -- not appear inside types, rather only as part of top-level function
    -- declarations.
    showsFunctionType (showParen True (x 0)) C.ImpureFunction (zipWith named [1..] args) res
  where
    named :: Int -> C.TypeFunArg p -> (ShowS, C.Type p)
    named i arg = (showString "arg" . shows i, arg.typ)
showsType x C.TypeVoid                 = showString "void " . x 0
showsType x (C.TypeExtBinding ref)     = showsId (Proxy @p) (extBindingId (Proxy @p) ref.name) . showChar ' ' . x 0
showsType x (C.TypeBlock t)            = showsType (\_d -> showString "^" . x 0) t
-- Type qualifiers like @const@ can appear before, and _after_ the type they
-- refer to. For example,
--
-- > const int x;
-- > int const x;
--
-- > const int f();
-- > int const f();
--
-- More involved: A function with a return type being a "constant pointer to
-- constant integer".
--
-- > const int * const f();
-- > int const * const f();
--
-- That is, for pointers, the @const@ qualifier is always written as a suffix!
-- For example, both of the following declarations declare a pointer to a
-- constant integer:
--
-- > int const * f();
-- > int const* f();
--
-- Did you know that stacked @const@ qualifiers are merged by the C parser:
--
-- > const int const * f(); // Parsed as "const int *".
-- > int const const * f(); // Parsed as "const int *".
--
-- It is somewhat difficult to correctly print the @const@ qualifier before
-- primitive types but after pointers. Hence, we consistently print @const@
-- _after_ the type. For example, we print return type "constant pointer to
-- constant int" as follows:
--
-- > int const * const f();
showsType x (C.TypeQual C.QualConst t) = showsType (\d -> showString "const " . x  d) t
showsType x (C.TypeComplex p) = C.showsPrimType p . showChar ' ' . showString "_Complex " . x 0

-- | The precedence of various constructs in C declarations.
type CTypePrecedence = Int

-- NOTE: picked somewhat arbitrarily to be larger than 'pointerPrec'
arrayPrec :: CTypePrecedence
arrayPrec = 10

-- NOTE: picked somewhat arbitrarily to be smaller than 'arrayPrec'
pointerPrec :: CTypePrecedence
pointerPrec = 5

-- | Show function purity in C syntax.
--
-- Function purity translates to a @const@ or @pure@ function attribute.
--
--
-- >>> import HsBindgen.Frontend.AST.Decl (FunctionPurity(..))
--
-- >>> showsFunctionPurity ImpureFunction ""
-- ""
--
-- >>> showsFunctionPurity HaskellPureFunction ""
-- "__attribute__ ((const))"
--
-- >>> showsFunctionPurity CPureFunction ""
-- "__attribute__ ((pure))"
showsFunctionPurity :: C.FunctionPurity -> ShowS
showsFunctionPurity pur = case pur of
    C.ImpureFunction -> id
    C.HaskellPureFunction -> withShowsAttribute "const"
    C.CPureFunction -> withShowsAttribute "pure"
  where
    withShowsAttribute s =
        showString "__attribute__ (("
      . showString s
      . showString "))"

-- | Print a newline after a function attribute, but only if there is an
-- attribute.
showAttributeNewline :: C.FunctionPurity -> ShowS
showAttributeNewline pur = case pur of
    C.ImpureFunction -> id
    C.HaskellPureFunction -> showChar '\n'
    C.CPureFunction -> showChar '\n'

showsId :: IsPass p => Proxy p -> Id p -> ShowS
showsId p declId =
    case idSourceName p declId of
      Just name -> showsDeclName name
      Nothing   -> panicPure $ "Cannot refer to unnamed decl " ++ show declId

showsDeclName :: C.DeclName -> ShowS
showsDeclName = showsText . C.renderDeclNameC

showsText :: Text -> ShowS
showsText = showString . Text.unpack