{-# LANGUAGE NamedFieldPuns #-} -- | The script that generates the source code for "Data.Tuple.Constraint" and -- "Data.Tuple.Constraint.ClassNewtype". See the @GenCTuple.sh@ script for how -- to invoke this. module Main (main) where import Data.List.Compat import GHC.Exts (maxTupleSize) import Prelude () import Prelude.Compat import Options.Applicative data Args = Args { output :: FilePath , classNewtype :: Bool } deriving Show argsParser :: Parser Args argsParser = Args <$> strOption ( long "output" <> short 'o' <> metavar "PATH" <> help "The file to which to write the source code" ) <*> switch ( long "class-newtype" <> help "Generate the source code for Data.Tuple.Constraint.ClassNewtype" ) main :: IO () main = execParser opts >>= generate where opts = info (argsParser <**> helper) ( fullDesc <> progDesc spiel <> header spiel ) spiel = "Generate the source code for Data.Tuple.Constraint{,.ClassNewtype}" generate :: Args -> IO () generate args@Args{output} = let sourceCode = unlines $ preamble args ++ decs args in writeFile output sourceCode genCTuple :: Args -> Int -> String genCTuple Args{classNewtype} n | n == 0 = "CTuple0" | otherwise = parens (concat (intersperse ", " cNums)) ++ " => CTuple" ++ show n ++ " " ++ unwords cNums where parens :: String -> String parens s | n == 1 = s | otherwise = kindSig $ "(" ++ s ++ ")" kindSig :: String -> String kindSig s | classNewtype = "(" ++ s ++ " :: Constraint)" | otherwise = s cNums :: [String] cNums = ['c':show i | i <- [1..n]] haddocks :: Int -> [String] haddocks i = [ "-- | A constraint tuple class with " ++ show i ++ " argument" ++ pluralSuffix ++ "." ] ++ if i == 0 then [ "--" , "-- This class is only defined on GHC 7.8 or later." ] else [] where pluralSuffix :: String pluralSuffix | i == 1 = "" | otherwise = "s" preamble :: Args -> [String] preamble Args{classNewtype} = [ "{-# LANGUAGE ConstraintKinds #-}" , "{-# LANGUAGE CPP #-}" , "{-# LANGUAGE FlexibleInstances #-}" , "{-# LANGUAGE KindSignatures #-}" , "{-# LANGUAGE MultiParamTypeClasses #-}" , "{-# LANGUAGE UndecidableInstances #-}" , "#if __GLASGOW_HASKELL__ >= 708 && __GLASGOW_HASKELL__ < 710" , "{-# LANGUAGE NullaryTypeClasses #-}" , "#endif" , "#if __GLASGOW_HASKELL__ >= 800" , "{-# LANGUAGE UndecidableSuperClasses #-}" , "#endif" ] ++ safeHaskell ++ [ "-- | This module provides classes that emulate the behavior of GHC's constraint" , "-- tuple syntax. Unlike GHC's built-in constraint tuples, the classes in this" , "-- library can be partially applied." , "--" ] ++ haddockNote ++ [ "module " ++ modName , " ( -- * Constraint tuples" ] ++ exports ++ [ " ) where" , "" ] ++ imports where modName :: String modName = "Data.Tuple.Constraint" ++ if classNewtype then ".ClassNewtype" else "" exports :: [String] exports = flip concatMap [0..maxTupleSize] $ \i -> case i of 0 -> [ "#if __GLASGOW_HASKELL__ >= 708" , " CTuple0," , "#endif" ] 1 -> [ " CTuple1" ] _ -> [ " , CTuple" ++ show i ] imports :: [String] imports | classNewtype = [ "import Data.Tuple.Constraint ( CTuple1" , "#if __GLASGOW_HASKELL__ >= 708" , " , CTuple0" , "#endif" , " )" , "#if __GLASGOW_HASKELL__ >= 800" , "import Data.Kind (Constraint)" , "#else" , "import GHC.Exts (Constraint)" , "#endif" , "" ] | otherwise = [] safeHaskell :: [String] safeHaskell | classNewtype = [ "#if __GLASGOW_HASKELL__ >= 800" , "{-# LANGUAGE Safe #-}" , "#else" , "{-# LANGUAGE Trustworthy #-}" , "#endif" ] | otherwise = [ "{-# LANGUAGE Safe #-}" ] haddockNote :: [String] haddockNote | classNewtype = [ "-- Unlike \"Data.Tuple.Constraint\", a @CTupleN@ class defined in this module" , "-- (where @N@ is greater than 1) compiles to a newtype around the corresponding" , "-- built-in constraint tuple type with @N@ arguments in Core. In contrast, a" , "-- @CTupleN@ class defined in \"Data.Tuple.Constraint\" compiles to a" , "-- dictionary data type with @N@ fields in Core." , "--" , "-- For most use cases, this distinction is of no practical consequence. One" , "-- scenario where you may benefit from using this module is when you are" , "-- interoperating with built-in constraint tuple syntax." , "-- For example, in this code:" , "--" , "-- @" , "-- data Dict :: Constraint -> Type where" , "-- Dict :: c => Dict c" , "--" , "-- foo :: CTuple2 a b => Dict (a, b)" , "-- foo = Dict" , "-- @" , "--" , "-- If you use the @CTuple2@ class from \"Data.Tuple.Constraint\" to define" , "-- @foo@, then in the Core for @foo@, the @a@ and @b@ must be extracted from" , "-- the @CTuple2@ dictionary before building the @Dict@ dictionary. On the other" , "-- hand, if you use the @CTuple@ class from this module, then no such" , "-- extraction is necessary, as the Core can simply cast the @CTuple2@" , "-- dictionary (which is a newtype) to the @(a, b)@ dictionary and use that to" , "-- construct a @Dict@ dictionary." ] | otherwise = [] decs :: Args -> [String] decs args@Args{classNewtype} = flip concatMap [0..maxTupleSize] $ \i -> let cTuple = genCTuple args i in if classNewtype && (i == 0 || i == 1) then [] -- CTuple{0,1} are imported from Data.Tuple.Constraint else concat [ [ "#if __GLASGOW_HASKELL__ >= 708" | i == 0 ] , haddocks i , [ "class " ++ cTuple , "instance " ++ cTuple ] , [ "#endif" | i == 0 ] , [ "" ] ]