packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Frontend/PrettyC.hs

-- | A small subset of C which we can pretty-print.
--
-- Used for generating C wrappers in userland-capi approach.
-- It's cleaner to generate AST than glueing string-of-code together.
module HsBindgen.Frontend.PrettyC (
    FunDefn (..),
    Args,
    withArgs,
    argsToIdx,
    -- * AST
    Statement (..),
    CompoundStatement (..),
    CSList (..),
    Declaration (..),
    Declarator (..),
    Initializer (..),
    LVal (..),
    Expr (..),
    -- * Pretty-printing
    prettyFunDefn,
) where

import Prelude hiding (lines, unlines)

import Control.Monad.State.Strict (State, evalState, get, put)
import DeBruijn (Env (..), Idx, lookupEnv, sizeEnv, sizeToInt, tabulateEnv)

import HsBindgen.Frontend.Pass.Final
import HsBindgen.Imports
import HsBindgen.IR.C qualified as C

type Name = String

data FunDefn where
    FunDefn :: Name -> C.Type Final -> C.FunctionPurity -> Args ctx -> CompoundStatement ctx -> FunDefn

deriving instance Show FunDefn

type Args ctx = Env ctx (C.Type Final)

-- Env, and thus Args, are SnocList.
-- when converting from ordinary list, we need to reverse first.
withArgs :: [a] -> (forall ctx. Env ctx a -> r) -> r
withArgs tys = withArgs' (reverse tys)

withArgs' :: [a] -> (forall ctx. Env ctx a -> r) -> r
withArgs' []       k = k EmptyEnv
withArgs' (x : xs) k = withArgs' xs $ \args -> k (args :> x)

argsToIdx :: Env ctx a -> Env ctx (Idx ctx)
argsToIdx args = tabulateEnv (sizeEnv args) id

{-------------------------------------------------------------------------------
  AST
-------------------------------------------------------------------------------}

-- | Statement
--
-- <https://en.cppreference.com/w/c/language/statements.html>
data Statement ctx where
    CompoundStatement :: CompoundStatement ctx -> Statement ctx
    -- | Expression statement
    --
    -- <https://en.cppreference.com/w/c/language/statements.html#Expression_statements>
    ExpressionStatement :: Expr ctx -> Statement ctx
  deriving Show

-- | Compound statement
--
-- <https://en.cppreference.com/w/c/language/statements.html#Compound_statements>
data CompoundStatement ctx where
    CSList :: CSList ctx -> CompoundStatement ctx
  deriving Show

-- | Compound statement (continued)
--
-- <https://en.cppreference.com/w/c/language/statements.html#Compound_statements>
data CSList ctx where
    CSNil :: CSList ctx
    CSStatement :: Statement ctx -> CSList ctx -> CSList ctx
    CSDeclaration :: Declaration ctx ctx' -> CSList ctx' -> CSList ctx

deriving stock instance Show (CSList ctx)

-- | @declaration@
--
-- <https://en.cppreference.com/w/c/language/declarations.html>
data Declaration ctx ctx' where
    -- | A declaration: no initializer
    Declaration :: C.Type Final -> Declarator ctx ctx' -> Declaration ctx ctx'
    -- | A definition: a declaration with an initializer
    Definition :: C.Type Final -> Declarator ctx ctx' -> Initializer ctx -> Declaration ctx ctx'

deriving stock instance Show (Declaration ctx ctx')

-- | @declarator@
--
-- <https://en.cppreference.com/w/c/language/declarations.html#Declarators>
data Declarator ctx ctx' where
    -- | An identifier
    Identifier :: Declarator ctx (S ctx)

deriving stock instance Show (Declarator ctx ctx')

-- | @initializer@
--
-- <https://en.cppreference.com/w/c/language/initialization.html>
data Initializer ctx where
    -- | @expression@
    InitializerExpr :: Expr ctx -> Initializer ctx

deriving stock instance Show (Initializer ctx)

data LVal ctx
    = LVar (Idx ctx)
    | LDeRef (LVal ctx)
  deriving Show

data Expr ctx
    = Call Name [Expr ctx]
    | Var (Idx ctx)
      -- | A named variable can be used to refer to variables that are free with
      -- respect to the enclosing 'HsBindgen.Frontend.AST.Decl.Decl'\/'HsBindgen.PrettyC.FunDefn'.
      --
      -- If a variable is bound by the function definition, use 'Var' instead.
      --
      -- For example, any global variable is a free variable with respect to a
      -- function definition:
      --
      -- > int i = 0;
      -- > void i_plus (int j) { i += j; }
      --
      -- With respect to the function definition, @i@ is a free variable, @j@ is
      -- a bound variable. A 'HsBindgen.PrettyC.FunDefn' describing @i_plus@ should use 'NamedVar'
      -- for @i@, and 'Var' for @j@.
    | NamedVar Name
    | DeRef (Expr ctx)
      -- | The @&@ C-operator.
    | Address (Expr ctx)
    | Return (Expr ctx)
    | Assign (LVal ctx) (Expr ctx)
  deriving Show

{-------------------------------------------------------------------------------
  Pretty-printing
-------------------------------------------------------------------------------}

prettyFunDefn :: FunDefn -> ShowS
prettyFunDefn (FunDefn n ty attrs args stmts) = prettyFunDefn' n ty attrs args stmts

prettyFunDefn' ::
     forall ctx.
     Name
  -> C.Type Final
  -> C.FunctionPurity
  -> Args ctx
  -> CompoundStatement ctx
  -> ShowS
prettyFunDefn' fun res pur args stmts =
      C.showsFunctionType (showString fun) pur args' res
    . showString "\n"
    . unlines (prettyCompoundStatement stmts env)
  where
    args0 :: State Int (Env ctx ((ShowS, C.Type Final), ShowS))
    args0 = forM args $ \ty -> do
        i <- get
        put $! i + 1
        let n = showString "arg" . shows i
        return ((n, ty), n)

    args1 = evalState args0 1
    args' = toList (fst <$> args1)
    env   = snd <$> args1

prettyStatement :: Statement ctx -> Env ctx ShowS -> [Line]
prettyStatement (CompoundStatement stmts) env = prettyCompoundStatement stmts env
prettyStatement (ExpressionStatement expr) env = [Line $ prettyExpr env expr . showChar ';']

prettyCompoundStatement :: CompoundStatement ctx -> Env ctx ShowS -> [Line]
prettyCompoundStatement (CSList stmts) env = concat
    [ [Line $ showChar '{']
    , tabs (prettyCSList stmts env)
    , [Line $ showChar '}']
    ]

prettyCSList :: CSList ctx -> Env ctx ShowS -> [Line]
prettyCSList CSNil _env = []
prettyCSList (CSStatement stmt stmts) env = concat
    [ prettyStatement stmt env
    , prettyCSList stmts env
    ]
prettyCSList (CSDeclaration decl stmts) env =
    let (s, env') = prettyDeclaration decl env
    in  s : prettyCSList stmts env'

prettyDeclaration ::Declaration ctx ctx' ->  Env ctx ShowS -> (Line, Env ctx' ShowS)
prettyDeclaration (Declaration t d) env =
    let (name, env') = prettyDeclarator d env
    in  ( Line $ C.showsVariableType name t . showChar ';'
        , env'
        )
prettyDeclaration (Definition t d e) env =
    let (name, env') = prettyDeclarator d env
    in  ( Line $ C.showsVariableType name t . showString " = " . prettyInitializer e env . showChar ';'
        , env'
        )

prettyDeclarator ::
     forall ctx ctx'.
     Declarator ctx ctx'
  -> Env ctx ShowS
  -> (ShowS, Env ctx' ShowS)
prettyDeclarator Identifier env = (name, env')
  where
    env' = env :> name
    name = showChar 'x' . shows (sizeToInt (sizeEnv env) + 1)

prettyInitializer :: Initializer ctx -> Env ctx ShowS -> ShowS
prettyInitializer (InitializerExpr e) env = prettyExpr env e

prettyLVal :: Env ctx ShowS -> LVal ctx -> ShowS
prettyLVal env (LVar x)   = lookupEnv x env
prettyLVal env (LDeRef x) = showChar '*' . prettyLVal env x

prettyExpr :: Env ctx ShowS -> Expr ctx -> ShowS
prettyExpr env  (Var s)      = lookupEnv s env
prettyExpr _env (NamedVar n) = showString n
prettyExpr env  (DeRef e)    = showChar '*' . prettyExpr env e
prettyExpr env  (Address e)  = showChar '&' . prettyExpr env e
prettyExpr env  (Call f xs)  = showParen True (showString f) . showParen True (foldMapSepShowS (showString ", ") (prettyExpr env) xs)
prettyExpr env  (Return e)   = showString "return " . prettyExpr env e
prettyExpr env  (Assign x e) = prettyLVal env x . showString " = " . prettyExpr env e

{-------------------------------------------------------------------------------
  Foldable
-------------------------------------------------------------------------------}

foldMapShowS :: (a -> ShowS) -> [a] -> ShowS
foldMapShowS f = foldr (\a b -> f a . b) id

foldMapSepShowS :: ShowS -> (a -> ShowS) -> [a] -> ShowS
foldMapSepShowS _sep _f []     = id
foldMapSepShowS  sep  f (x:xs) = foldr1 (\a b -> a . sep . b) (fmap f (x :| xs))

{-------------------------------------------------------------------------------
  Lines
-------------------------------------------------------------------------------}

-- | A single line of text (to be pretty-printed)
newtype Line = Line ShowS

unlines :: [Line] -> ShowS
unlines = foldMapShowS $ \(Line s) ->
    s . showChar '\n'

tabs :: [Line] -> [Line]
tabs xs = fmap tab xs

tab :: Line -> Line
tab (Line s) = Line (showString "  " . s)