packages feed

hs-wayland-scanner-0.1.0: src/Graphics/Wayland/Scanner/Text.hs

{-# LANGUAGE OverloadedStrings #-}
------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Wayland.Scanner.Text
-- Copyright   :  (c) Andrea Rossato 2026
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  andrea.rossato@unitn.it
-- Stability   :  stable
-- Portability :  portable
--
-- This module exports some functions for 'Text' manipulation and some
-- templates.
------------------------------------------------------------------------
module Graphics.Wayland.Scanner.Text where

import Data.Char
import Data.Text (Text)
import qualified Data.Text as T

import Graphics.Wayland.Scanner.Types

capitalize :: Text -> Text
capitalize t =
  case T.uncons t of
    Nothing -> t
    Just (c, rest) -> T.cons (toUpper c) rest

lowerFirst :: Text -> Text
lowerFirst t =
  case T.uncons t of
    Nothing -> t
    Just (c, rest) -> T.cons (toLower c) rest

toCamel :: Text -> Text
toCamel t =
  let parts = T.splitOn "_" t
  in case parts of
       []     -> t
       (x:xs) -> x <> T.concat (map capitalize xs)

toHsType :: Text -> Text
toHsType = capitalize . toCamel

toHsFcn :: Text -> Text
toHsFcn = lowerFirst . toCamel

wordWrap :: Int -> Text -> Text
wordWrap maxLen = unlines' . concatMap (wrapLine maxLen) . T.lines

wrapLine :: Int -> Text -> [Text]
wrapLine maxLen line = go (T.words line) T.empty []
  where
    go [] cur acc = map haddockIdent $ reverse $ flush cur acc
    go (w:ws) cur acc
      | T.null cur          = go ws w acc
      | T.length cur + 1 + T.length w <= maxLen
                            = go ws (cur <> " " <> w) acc
      | otherwise           = go ws w (cur : acc)
    flush cur acc = if T.null cur then acc else cur : acc

haddockIdent :: Text -> Text
haddockIdent = T.unwords . map go . T.words
  where
    go w
      | "__" `T.isInfixOf` w = w
      | "_"  `T.isInfixOf` w = "@" <> w <> "@"
      | otherwise            = w

formatHaddockSec :: Text -> Text
formatHaddockSec t =
  unlines'
  [ "-- * " <> t
  , "--"
  , "-- $"  <> T.replace " " "_" t
  ]

formatHaddockSubSec :: Text -> Text
formatHaddockSubSec t =
  unlines'
  [ "-- ** " <> t
  , "--"
  , "-- $"   <> T.replace " " "_" t
  ]

formatArgs :: [Text] -> Text
formatArgs [] = ""
formatArgs as = T.intercalate "\n    -> " as

formatArgComment :: Text -> Text
formatArgComment "" = ""
formatArgComment c = "-- ^ " <> haddockIdent c

formatTopLevelComment :: Text -> Text
formatTopLevelComment "" = ""
formatTopLevelComment c =
  unlines'
  [ "{- |"
  , T.intercalate "\n\n" $ map (wordWrap 80) $ T.lines c
  , "-}"
  ]

formatHaddockHeader :: Name -> Text -> Text
formatHaddockHeader name desc =
  unlines'
  [ "{- |"
  , "Module      :  " <> name
  , "Copyright   :  Public Domain (generated by hs-wayland-scanner)"
  , "License     :  Public Domain (generated by hs-wayland-scanner)"
  , ""
  , "Maintainer  :  <https://codeberg.org/andrea_rossato/hs-wayland-scanner>"
  , "Stability   :  stable"
  , "Portability :  portable"
  , ""
  , T.intercalate "\n\n" $ map (wordWrap 80) $ T.lines desc
  , "-}"
  ]

moduleHeader :: HwsConfig -> SolvedProtocol -> [Text]
moduleHeader (HwsConfig _ nameSpace role _ _ _) (SolvedProtocol name desc _ deps _) =
  [ "{-# LANGUAGE ForeignFunctionInterface #-}"
  , "{-# OPTIONS_GHC -Wno-unused-imports #-}"
  , formatHaddockHeader modName desc
  , "module " <> modName <> " where"
  , ""
  , if name /= "wayland" then "import "<> T.pack nameSpace <> "." <> "Wayland.Protocol.Wayland" else ""
  , "import "<> T.pack nameSpace <> "." <> "Wayland." <> modRole <> ".Core"
  , "import "<> T.pack nameSpace <> "." <> "Wayland.Protocol." <> toHsType name
  , ""
  , "import Foreign"
  , "import Foreign.C.String"
  , "import Foreign.C.Types"
  , imports
  , "#include \"" <> name <> "-" <> T.toLower modRole <> "-protocol.h\"\n"
  ]
  where
    modName  = T.pack nameSpace  <> ".Wayland." <> modRole <> ".Protocol." <> toHsType name
    modRole  = T.pack $ show role
    modImp d = "import " <> T.pack nameSpace <> ".Wayland." <> modRole <> ".Protocol." <> toHsType d
    imports  = T.unlines $ map modImp deps

moduleEnumHeader :: HwsConfig -> SolvedProtocol -> [Text]
moduleEnumHeader (HwsConfig _ nameSpace role _ _ _) p =
  [ "{-# LANGUAGE ForeignFunctionInterface #-}"
  , "{-# LANGUAGE PatternSynonyms #-}"
  , formatHaddockHeader modName "Data types and 'enum' shared by the client and server protocols."
  , "module " <> modName <> " where"
  , ""
  , "import Foreign"
  , "#include \"" <> solvedProtoName p <> "-" <> T.toLower modRole <> "-protocol.h\"\n"
  ] ++
  if solvedProtoName p /= "wayland" then [] else
    [ "data WlArray"
    , "data WlRegistry"
    , "data WlDisplay"
    , "data WlInterface"
    ]
  where
    modName  = T.pack nameSpace <> ".Wayland.Protocol." <> toHsType (solvedProtoName p)
    modRole  = T.pack $ show role

-- | The core modules implement wayland-server-core and
-- wayland-client-core.
moduleCore :: HwsConfig -> [Text]
moduleCore c@(HwsConfig _ _ Client _ _ _) = moduleClientCore c
moduleCore c@(HwsConfig _ _ Server _ _ _) = moduleServerCore c

moduleClientCore :: HwsConfig -> [Text]
moduleClientCore (HwsConfig _ nameSpace role _ _ _) =
  [ formatHaddockHeader modName "An implementation of the wayland-client-core library"
  , "module " <> modName <> ".Core where\n"
  , "import Foreign"
  , "import Foreign.C.String"
  , "import Foreign.C.Types\n"
  , "import "<> T.pack nameSpace <> ".Wayland.Protocol.Wayland\n"
  , "data WlEventQueue"
  , ""
  , "-- Core connection"
  , "foreign import ccall \"wl_display_connect\""
  , "  wl_display_connect :: CString -> IO (Ptr WlDisplay)"
  , ""
  , "foreign import ccall \"wl_display_disconnect\""
  , "  wl_display_disconnect :: Ptr WlDisplay -> IO ()"
  , ""
  , "foreign import ccall \"wl_display_get_fd\""
  , "  wl_display_get_fd :: Ptr WlDisplay -> IO CInt"
  , ""
  , "foreign import ccall \"wl_display_roundtrip\""
  , "  wl_display_roundtrip :: Ptr WlDisplay -> IO CInt"
  , ""
  , "-- Buffer management"
  , "foreign import ccall \"wl_display_flush\""
  , "  wl_display_flush :: Ptr WlDisplay -> IO CInt"
  , ""
  , "-- Processes incoming events. Blocks until events are read."
  , "foreign import ccall \"wl_display_dispatch\""
  , "  wl_display_dispatch :: Ptr WlDisplay -> IO CInt"
  ]
  where
    modName = T.pack nameSpace <> ".Wayland." <> T.pack (show role)

moduleServerCore :: HwsConfig -> [Text]
moduleServerCore (HwsConfig _ nameSpace role _ _ _) =
  [ formatHaddockHeader modName "An implementation of the wayland-server-core library"
  , "module " <> modName <> ".Core where\n"
  , "import Foreign"
  , "import Foreign.C.String"
  , "import Foreign.C.Types\n"
  , "import "<> T.pack nameSpace <> ".Wayland.Protocol.Wayland\n"
  , "data WlClient"
  , "data WlResource"
  , "data WlGlobal"
  , ""
  , "foreign import ccall \"wl_display_create\""
  , "  wl_display_create :: IO (Ptr WlDisplay)"
  , ""
  , "foreign import ccall \"wl_display_destroy\""
  , "  wl_display_destroy :: Ptr WlDisplay -> IO ()"
  , ""
  , "foreign import ccall \"wl_display_run\""
  , "  wl_display_run :: Ptr WlDisplay -> IO ()"
  , ""
  , "foreign import ccall \"wl_display_add_socket_fd\""
  , "  wl_display_add_socket_fd :: Ptr WlDisplay -> CInt -> IO CInt"
  , ""
  , "foreign import ccall \"wl_display_add_socket_auto\""
  , "  wl_display_add_socket_auto :: Ptr WlDisplay -> IO CString"
  , ""
  , "type WlGlobalBindFuncCb = Ptr WlClient -> Ptr () -> Word32 -> Word32 -> IO ()"
  , ""
  , "foreign import ccall \"wl_global_create\""
  , "  wl_global_create"
  , "    :: Ptr WlDisplay"
  , "    -> Ptr WlInterface"
  , "    -> CInt"
  , "    -> Ptr ()"
  , "    -> FunPtr WlGlobalBindFuncCb"
  , "    -> IO (Ptr WlGlobal)"
  , ""
  , "foreign import ccall \"wrapper\""
  , "  mkWlGlobalBindFuncCb :: WlGlobalBindFuncCb -> IO (FunPtr WlGlobalBindFuncCb)"
  , ""
  , "foreign import ccall \"wl_resource_create\""
  , "  wl_resource_create"
  , "    :: Ptr WlClient"
  , "    -> Ptr WlInterface"
  , "    -> CInt"
  , "    -> Word32"
  , "    -> IO (Ptr WlResource)"
  , ""
  , "foreign import ccall \"wl_resource_destroy\""
  , "  wl_resource_destroy :: Ptr WlResource -> IO ()"
  , ""
  , "type WlResourceDestroyFunc = Ptr WlResource -> IO ()"
  , ""
  , "foreign import ccall \"wl_resource_set_implementation\""
  , "  wl_resource_set_implementation"
  , "    :: Ptr WlResource"
  , "    -> Ptr ()                         -- ^ interface struct (opaque pointer): cast with 'castPtr'"
  , "    -> Ptr ()                         -- ^ user data"
  , "    -> FunPtr WlResourceDestroyFunc   -- ^ destroy callback"
  , "    -> IO ()"
  , ""
  , "foreign import ccall \"wrapper\""
  , "  mkWlResourceDestroyFunc :: WlResourceDestroyFunc -> IO (FunPtr WlResourceDestroyFunc)"
  , ""
  , "foreign import ccall \"wl_resource_post_event\""
  , "  wl_resource_post_event :: Ptr WlResource -> Word32 -> IO ()"
  ]
  where
    modName = T.pack nameSpace <> ".Wayland." <> T.pack (show role)

autogenComment :: Text
autogenComment =
  "/* Auto-generated by hs-wayland-scanner.\n" <>
  "Do not manually edit unless you know what you are doing. */\n\n"

autogenWrapperComment :: Text
autogenWrapperComment =
  "/* Auto-generated by hs-wayland-scanner.\n" <>
  "Aggregates Wayland protocol sources and FFI wrappers.\n" <>
  "Do not compile individual protocol .c files separately. */\n\n"

unlines' :: [Text] -> Text
unlines' = T.intercalate "\n"