OpenGLRaw-2.0.0.0: RegistryProcessor/src/Main.hs
module Main ( main ) where
import qualified Control.Monad as CM
import qualified Data.Char as C
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Maybe as DM
import qualified Data.Set as S
import qualified System.Console.GetOpt as G
import qualified System.Environment as E
import MangledRegistry
import ManPages
data Option
= PrintFeature
| PrintTokens
| PrintFunctions
| UseApi API
| UseVersion Version
| UseProfile ProfileName
deriving Eq
options :: [G.OptDescr Option]
options =
[ G.Option ['F'] ["print-feature"] (G.NoArg PrintFeature) "print feature"
, G.Option ['t'] ["print-tokens"] (G.NoArg PrintTokens) "print tokens"
, G.Option ['f'] ["print-functions"] (G.NoArg PrintFunctions) "print functions"
, G.Option ['a'] ["api"] (G.ReqArg (UseApi . API) "API") "extract features for API (default: gl)"
, G.Option ['v'] ["version"] (G.ReqArg (UseVersion . read) "VERSION") "extract features for version (default: 4.5)"
, G.Option ['p'] ["profile"] (G.ReqArg (UseProfile . ProfileName) "PROFILE") "extract features for profile (default: compatibility)" ]
getPaths :: IO ([Option], FilePath)
getPaths = do
args <- E.getArgs
case G.getOpt G.Permute options args of
(opts, [path], []) -> return (opts, path)
(_, _, errs) -> do
n <- E.getProgName
let header = "Usage: " ++ n ++ " [OPTION]... file"
ioError (userError (concat errs ++ G.usageInfo header options))
main :: IO ()
main = do
(opts, path) <- getPaths
let api = head ([ a | UseApi a <- opts ] ++ [ API "gl" ])
version = head ([ v | UseVersion v <- opts ] ++ [ read "4.5" ])
profile = head ([ p | UseProfile p <- opts ] ++ [ ProfileName "compatibility" ])
res <- fmap parseRegistry $ readFile path
case res of
Left msg -> putStrLn msg
Right registry -> do
CM.when (PrintFeature `elem` opts) $ do
let modName = "Graphics.Rendering.OpenGL.Raw." ++
capitalize (unProfileName profile) ++
show (major version) ++ show (minor version)
putStrLn "--------------------------------------------------------------------------------"
putStrLn "-- |"
putStrLn $ "-- Module : " ++ modName
putStrLn "-- Copyright : (c) Sven Panne 2015"
putStrLn "-- License : BSD3"
putStrLn "--"
putStrLn "-- Maintainer : Sven Panne <svenpanne@gmail.com>"
putStrLn "-- Stability : stable"
putStrLn "-- Portability : portable"
putStrLn "--"
putStrLn "--------------------------------------------------------------------------------"
putStrLn ""
let (ts,es,cs) = fixedGetTyEnCo api version profile registry
putStrLn $ "module "++ modName ++ " ("
CM.unless (null ts) $ do
putStrLn " -- * Types"
putStr $ separate unTypeName ts
putStrLn $ if null es && null cs then "" else ","
CM.unless (null es) $ do
putStrLn " -- * Enums"
putStr $ separate (unEnumName . enumName) es
putStrLn $ if null cs then "" else ","
CM.unless (null cs) $ do
putStrLn " -- * Functions"
putStr $ separate (unCommandName . commandName) cs
putStrLn ""
putStrLn ") where"
putStrLn ""
putStrLn "import Graphics.Rendering.OpenGL.Raw.Types"
putStrLn "import Graphics.Rendering.OpenGL.Raw.Tokens"
putStrLn "import Graphics.Rendering.OpenGL.Raw.Functions"
CM.when (PrintTokens `elem` opts) $ do
putStrLn "--------------------------------------------------------------------------------"
putStrLn "-- |"
putStrLn "-- Module : Graphics.Rendering.OpenGL.Raw.Tokens"
putStrLn "-- Copyright : (c) Sven Panne 2015"
putStrLn "-- License : BSD3"
putStrLn "--"
putStrLn "-- Maintainer : Sven Panne <svenpanne@gmail.com>"
putStrLn "-- Stability : stable"
putStrLn "-- Portability : portable"
putStrLn "--"
putStrLn "-- All enumeration tokens from the OpenGL registry, see"
putStrLn "-- <http://www.opengl.org/registry/>."
putStrLn "--"
putStrLn "--------------------------------------------------------------------------------"
putStrLn ""
putStrLn "module Graphics.Rendering.OpenGL.Raw.Tokens where"
putStrLn ""
putStrLn "import Graphics.Rendering.OpenGL.Raw.Types"
putStrLn ""
mapM_ (putStrLn . unlines . convertEnum)
[ e
| es <- M.elems (enums registry)
, e <- es
, api `matches` enumAPI e ]
CM.when (PrintFunctions `elem` opts) $ do
putStrLn "{-# LANGUAGE CPP #-}"
putStrLn "--------------------------------------------------------------------------------"
putStrLn "-- |"
putStrLn "-- Module : Graphics.Rendering.OpenGL.Raw.Functions"
putStrLn "-- Copyright : (c) Sven Panne 2015"
putStrLn "-- License : BSD3"
putStrLn "--"
putStrLn "-- Maintainer : Sven Panne <svenpanne@gmail.com>"
putStrLn "-- Stability : stable"
putStrLn "-- Portability : portable"
putStrLn "--"
putStrLn "-- All raw functions from the OpenGL registry, see"
putStrLn "-- <http://www.opengl.org/registry/>."
putStrLn "--"
putStrLn "--------------------------------------------------------------------------------"
putStrLn ""
putStrLn "module Graphics.Rendering.OpenGL.Raw.Functions ("
putStrLn . separate unCommandName . M.keys . commands $registry
putStrLn ") where"
putStrLn ""
putStrLn "import Foreign.C.Types"
putStrLn "import Foreign.Marshal.Error ( throwIf )"
putStrLn "import Foreign.Ptr ( Ptr, FunPtr, nullFunPtr )"
putStrLn "import System.IO.Unsafe ( unsafePerformIO )"
putStrLn ""
putStrLn "import Graphics.Rendering.OpenGL.Raw.GetProcAddress ( getProcAddress )"
putStrLn "import Graphics.Rendering.OpenGL.Raw.Types"
putStrLn ""
putStrLn "getCommand :: String -> IO (FunPtr a)"
putStrLn "getCommand cmd ="
putStrLn " throwIfNullFunPtr (\"unknown OpenGL command \" ++ cmd) $ getProcAddress cmd"
putStrLn ""
putStrLn "throwIfNullFunPtr :: String -> IO (FunPtr a) -> IO (FunPtr a)"
putStrLn "throwIfNullFunPtr = throwIf (== nullFunPtr) . const"
putStrLn ""
mapM_ (putStrLn . showCommand api) (M.elems (commands registry))
capitalize :: String -> String
capitalize str = C.toUpper (head str) : tail str
separate :: (a -> String) -> [a] -> String
separate f = L.intercalate ",\n" . map (" " ++) . map f
-- Annoyingly enough, the OpenGL registry doesn't contain any enums for
-- OpenGL 1.0, so let's just use the OpenGL 1.1 ones.
fixedGetTyEnCo :: API -> Version -> ProfileName -> Registry -> ([TypeName],[Enum'],[Command])
fixedGetTyEnCo api version profile registry
| api == API "gl" && version == read "1.0" = (ts, es11, cs)
| otherwise = tec
where tec@(ts, _, cs) = getTyEnCo api version profile registry
(_, es11, _) = getTyEnCo api (read "1.1") profile registry
getTyEnCo :: API -> Version -> ProfileName -> Registry -> ([TypeName],[Enum'],[Command])
getTyEnCo api version profile registry = (ts', es, cs)
where ts = [ n | TypeElement n <- lst ]
es = [ e | EnumElement n <- lst
, e <- lookup' n (enums registry)
, api `matches` enumAPI e ]
cs = [ lookup' n (commands registry) | CommandElement n <- lst ]
-- Features don't explicitly list the types referenced by commands.
ts' = S.toList . S.unions $ S.fromList ts : map referencedTypes cs
lst = S.toList $ interfaceElementsFor api version profile registry
-- Here is the heart of the feature construction logic: Chronologically replay
-- the whole version history for the given API/version/profile triple.
interfaceElementsFor :: API -> Version -> ProfileName -> Registry -> S.Set InterfaceElement
interfaceElementsFor api version profile registry =
foldl (flip ($)) S.empty modificationsFor
where modificationsFor =
[ op (modificationKind m) ie
| key <- L.sort keys
, m <- lookup' key (features registry)
, profile `matches` modificationProfile m
, ie <- modificationInterfaceElements m ]
keys = [ key
| key@(a,v) <- M.keys (features registry)
, a == api
, v <= version ]
op Require = S.insert
op Remove = S.delete
lookup' :: (Ord k, Show k) => k -> M.Map k a -> a
lookup' k m = M.findWithDefault (error ("unknown name " ++ show k)) k m
matches :: Eq a => a -> Maybe a -> Bool
_ `matches` Nothing = True
s `matches` Just t = s == t
convertEnum :: Enum' -> [String]
convertEnum e =
[ n ++ " :: " ++ unTypeName (enumType e)
, n ++ " = " ++ unEnumValue (enumValue e) ]
where n = unEnumName . enumName $ e
showCommand :: API -> Command -> String
showCommand api c =
showString (take 80 ("-- " ++ name ++ " " ++ repeat '-') ++ "\n\n") .
showString man .
showString (name ++ "\n") .
showString (" :: " ++ signature True) .
showString (name ++ " = " ++ dyn_name ++ " " ++ ptr_name ++ "\n\n") .
showString ("foreign import CALLCONV unsafe \"dynamic\" " ++ dyn_name ++ "\n" ++
" :: FunPtr (" ++ compactSignature ++ ")\n" ++
" -> " ++ compactSignature ++ "\n\n") .
showString ("{-# NOINLINE " ++ ptr_name ++ " #-}\n") .
showString (ptr_name ++ " :: FunPtr (" ++ compactSignature ++ ")\n") .
showString (ptr_name ++ " = unsafePerformIO $ getCommand " ++ str_name ++ "\n") .
id $ ""
where name = signatureElementName (resultType c)
dyn_name = "dyn_" ++ name
ptr_name = "ptr_" ++ name
str_name = show name
compactSignature = signature False
signature withComment =
L.intercalate ((if withComment then " " else "") ++ " -> ")
([showSignatureElement withComment False t | t <- paramTypes c] ++
[showSignatureElement withComment True (resultType c)])
urls = M.findWithDefault [] (api, CommandName name) manPageURLs
links = L.intercalate " or " (map renderURL urls) ++ "\n"
man = case urls of
[] -> ""
[_] -> "-- | Manual page for " ++ links
_ -> "-- | Manual pages for " ++ links
renderURL (u, l) = "<" ++ u ++ " " ++ l ++ ">"
showSignatureElement :: Bool -> Bool -> SignatureElement -> String
showSignatureElement withComment isResult sigElem = el ++ comment
where el | isResult = "IO " ++ showsPrec 11 sigElem ""
| otherwise = show sigElem
comment | withComment = showComment name sigElem
| otherwise = ""
name | isResult = ""
| otherwise = signatureElementName sigElem
showComment :: String -> SignatureElement -> String
showComment name sigElem
| null name' && null info = "\n"
| otherwise = " -- ^" ++ name' ++ info ++ ".\n"
where name' | null name = ""
| otherwise = " " ++ inlineCode name
info | isInteresting = elms ++ " of type " ++ inlineCode (show (base sigElem))
| otherwise = ""
isInteresting = DM.isJust (arrayLength sigElem) || DM.isJust (belongsToGroup sigElem)
elms | numPointer sigElem > 0 = " pointing to" ++ len ++ " elements"
| otherwise = ""
len = maybe "" (\l -> " " ++ inlineCode l) (arrayLength sigElem)
base = maybeDeref . maybeSetBaseType
maybeDeref e | numPointer e > 0 = e{numPointer = numPointer e - 1}
| otherwise = e
maybeSetBaseType e = maybe e (\g -> e{baseType = TypeName (unGroupName g)}) (belongsToGroup e)
inlineCode :: String -> String
inlineCode s = "@" ++ s ++ "@"