packages feed

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

{-# LANGUAGE OverloadedStrings #-}
------------------------------------------------------------------------
-- |
-- Module      :  Graphics.Wayland.Scanner.Generate
-- 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 generating Haskell and C
-- code.
------------------------------------------------------------------------
module Graphics.Wayland.Scanner.Generate where

import Control.Monad
import Data.Char
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.Text.IO as T
import Text.XML.Light
import System.Directory
import System.FilePath
import System.Process

import Graphics.Wayland.Scanner.Parse
import Graphics.Wayland.Scanner.Render
import Graphics.Wayland.Scanner.RenderC
import Graphics.Wayland.Scanner.Solve
import Graphics.Wayland.Scanner.Text
import Graphics.Wayland.Scanner.Types

-- | File generation
generate' :: HwsConfig -> [Text] -> IO ()
generate' cfg = void . generate cfg

-- | Returns the list of generated Haskell modules
generate :: HwsConfig -> [Text] -> IO [Text]
generate cfg@(HwsConfig prefix nameSpace role _ cbits _) xmlSources = do
  let parseDoc i =
        case parseXMLDoc i of
          Nothing  -> error ("invalid XML: " ++ show i)
          Just res -> res
      roleName     = if role == Server then "server" else "client"
      docs         = map parseDoc      xmlSources
      parsedProtos = map parseProtocol docs
      ifaceMap     = buildIfaceMap     parsedProtos
      solvedProtos = map (solveProtocol ifaceMap) parsedProtos
      include p    = T.pack $
                     "#include \"" ++ T.unpack (solvedProtoName p) ++ "-" ++ roleName ++ "-protocol.c\"\n" ++
                     "#include \"" ++ T.unpack (solvedProtoName p) ++ "-" ++ roleName ++ ".c\""
  generateDirs       cfg
  generateModuleCore cfg
  mapM_     (generateModule     cfg) solvedProtos
  zipWithM_ (generateCbits      cfg) (protocols cfg) solvedProtos
  mapM_     (generateFFIWrapper cfg) solvedProtos
  T.writeFile (prefix </> cbits </> "wayland-" ++ roleName ++ "-protocols.c") $
    T.unlines $ autogenWrapperComment : map include solvedProtos
  let generated  p = T.intercalate "." $ map toHsType [T.pack nameSpace, "Wayland", T.pack roleName, "Protocol", solvedProtoName p]
      defModules p = T.intercalate "." $ map toHsType [T.pack nameSpace, "Wayland.Protocol", solvedProtoName p]
      coreModule   = T.intercalate "." $ map toHsType [T.pack nameSpace, "Wayland", T.pack roleName, "Core"]
      modules    p = [generated p, defModules p]
  return $ coreModule : concatMap modules solvedProtos

generateDirs :: HwsConfig -> IO ()
generateDirs (HwsConfig prefix nameSpace role _ cbits src) = do
  let dir  = T.unpack $ T.replace "." "/"  $ T.pack nameSpace
  createDirectoryIfMissing True $ prefix </> src </> dir </> "Wayland" </> show role </> "Protocol"
  createDirectoryIfMissing True $ prefix </> src </> dir </> "Wayland/Protocol"
  createDirectoryIfMissing True $ prefix </> cbits

generateModule :: HwsConfig -> SolvedProtocol -> IO ()
generateModule cfg@(HwsConfig prefix nameSpace role _ _ src) proto = do
  let dir       = T.unpack $ T.replace "." "/"  $ T.pack nameSpace
      hsCode    = renderProtocol role proto
      roleName  = show role
      protoFile = src </> dir </> "Wayland" </> roleName </> "Protocol" </> T.unpack (toHsType $ solvedProtoName proto)
      enumsFile = src </> dir </> "Wayland" </> "Protocol" </> T.unpack (toHsType $ solvedProtoName proto)
  T.writeFile (prefix </> protoFile ++ ".hsc") $ T.unlines $ moduleHeader     cfg proto ++ map snd hsCode
  T.writeFile (prefix </> enumsFile ++ ".hsc") $ T.unlines $ moduleEnumHeader cfg proto ++ map fst hsCode

generateModuleCore :: HwsConfig -> IO ()
generateModuleCore cfg@(HwsConfig prefix nameSpace role _ _ src) = do
  let dir  = T.unpack $ T.replace "." "/"  $ T.pack nameSpace
  T.writeFile (prefix </> src </> dir </> "Wayland" </> show role </> "Core.hs") $ T.unlines $ moduleCore cfg

generateCbits :: HwsConfig -> FilePath -> SolvedProtocol -> IO ()
generateCbits (HwsConfig prefix _ role _ cbits _) infile proto = do
  let roleName = map toLower $ show role
      hFileOpts = [roleName ++ "-header", infile, prefix </> cbits </> T.unpack (solvedProtoName proto) ++ "-" ++ roleName ++ "-protocol.h"]
      cFileOpts = ["private-code",        infile, prefix </> cbits </> T.unpack (solvedProtoName proto) ++ "-" ++ roleName ++ "-protocol.c"]
  mapM_ (callProcess "wayland-scanner") [hFileOpts,cFileOpts]

generateFFIWrapper :: HwsConfig -> SolvedProtocol -> IO ()
generateFFIWrapper (HwsConfig prefix _ role _ cbits _) (SolvedProtocol name _ ifaces _ _) = do
  let roleName = map toLower $ show role
      gen (Interface iface _ _ evs reqs _ _) =
        renderCWrapper role iface (if role == Server then evs else reqs) <>
        if null evs || role == Server then "" else renderCListener iface
      file = autogenComment <> "#include <" <> name <> "-" <> T.pack roleName <> "-protocol.h>\n"  <> T.unlines (map gen ifaces)
  T.writeFile (prefix </> cbits </> T.unpack name ++ "-" ++ roleName ++ ".c") file