-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathSetup.hs
145 lines (122 loc) · 5.07 KB
/
Setup.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
module Main where
import Data.Char (isSpace)
import Data.Functor ((<&>))
import Data.Text (Text)
import Distribution.Simple
import Distribution.Simple.PreProcess
import Distribution.Simple.Utils
import Distribution.Types.BuildInfo (BuildInfo)
import Distribution.Types.LocalBuildInfo (LocalBuildInfo)
import Distribution.Types.ComponentLocalBuildInfo (ComponentLocalBuildInfo)
import System.Directory (getTemporaryDirectory)
import System.IO (hClose)
import qualified Data.Text as T
import qualified Data.Text.IO as T
main :: IO ()
main = defaultMainWithHooks simpleUserHooks
{ -- override existing extension so Cabal has a file extension it knows already
hookedPreProcessors = [("hsc", ppHscJinja)]
}
ppHscJinja :: BuildInfo -> LocalBuildInfo -> ComponentLocalBuildInfo -> PreProcessor
ppHscJinja bi lbi clbi = PreProcessor
{ platformIndependent = False
, ppOrdering = unsorted
, runPreProcessor = mkSimplePreProcessor $ \inFile outFile verbosity -> do
source <- T.readFile inFile
case process source of
Left err -> do
die' verbosity $ "in file " ++ inFile ++ ": " ++ err
Right result -> do
-- put result into a temporary file, then run existing
-- hsc2hs preprocessor on that
tmp <- getTemporaryDirectory
withTempFile tmp (asTemplate inFile) $ \tmpFile handle -> do
debug verbosity $ "HscJinja: got temporary file: " ++ tmpFile
T.hPutStr handle result
hClose handle -- make sure to finalise everything before hsc2hs reads it
runSimplePreProcessor
(ppHsc2hs bi lbi clbi)
tmpFile outFile verbosity
}
asTemplate :: String -> String
asTemplate = fmap $ \case
'/' -> '-'
'\\' -> '-'
c -> c
process :: Text -> Either String Text
process = fmap T.concat . traverse go . T.splitOn "{{"
where
go :: Text -> Either String Text
go t = case T.breakOn "}}" t of
(directive, after)
| T.null after -> Right t -- before the first {{
| otherwise ->
let (macro, args) = T.break isSpace $ T.strip directive
args' = T.strip <$> T.splitOn "," args
result = case T.strip macro of
"struct" -> Right $ mkStruct args'
"enum" -> Right $ mkEnum args'
m -> Left $ T.unpack $ "unknown macro: " <> m
after' = T.drop 2 after -- get rid of }}
in (<> after') <$> result
mkStruct :: [Text] -> Text
mkStruct args = dataDecl <> storableDecl
where
(cfile:ctype:fields') = args
fields = pairs $ fields'
hstype =
let (prefix, t) = T.break (=='_') ctype
in T.toUpper prefix <> t
asHsField n = ctype <> "_" <> asField "_" n
asCField n = asField "." n
splitFieldType :: Text -> (Maybe Text, Text)
splitFieldType (T.stripPrefix "[" -> Just t') =
let (n, T.stripPrefix "]" -> Just t) = T.break (==']') t'
in (Just n, "[" <> t <> "]")
splitFieldType t = (Nothing, t)
dataDecl =
T.concat [ "data {-# CTYPE \"" , cfile , "\" \"struct ", ctype, "\" #-} " , hstype]
<> (if (null fields')
then ""
else T.concat [" = ", hstype, " { ", recordFields, " }"])
<> " deriving Show"
recordFields = T.intercalate ", " $
fields <&> \(n, t) -> asHsField n <> " :: " <> snd (splitFieldType t)
storableDecl
| null fields' = ""
| otherwise =
"\n\ninstance Storable " <> hstype
<> " where\n alignment _ = #alignment struct " <> ctype
<> "\n sizeOf _ = #size struct " <> ctype
<> "\n peek ptr = " <> hstype <> " <$> " <> peekImpl
<> "\n poke ptr t = " <> pokeImpl
peekImpl = T.intercalate " <*> " $
fields <&> \(n, t) -> case splitFieldType t of
(Nothing, _) -> "(#peek struct " <> ctype <> ", " <> asCField n <> ") ptr"
(Just m, _) -> "peekArray " <> m <> "((#ptr struct " <> ctype <> ", " <> asCField n <> ") ptr)"
pokeImpl = T.intercalate " >> " $
fields <&> \(n, t) -> case splitFieldType t of
(Nothing, _) ->
"(#poke struct " <> ctype <> ", " <> asCField n
<> ") ptr (" <> asHsField n <> " t)"
(Just _, _) ->
"pokeArray ((#ptr struct " <> ctype <> ", " <> asCField n
<> ") ptr) (" <> asHsField n <> " t)"
mkEnum :: [Text] -> Text
mkEnum args = enumType <> "\n" <> enumPatterns
where
(hstype:rest) = args
enumType = "type " <> hstype <> " = CInt"
enumPatterns = T.unlines $
rest >>= \val ->
[ "pattern " <> val <> " :: (Eq a, Num a) => a"
, "pattern " <> val <> " = #const " <> val
]
pairs :: [a] -> [(a, a)]
pairs (a:b:as) = (a,b) : pairs as
pairs _ = []
asField :: Text -> Text -> Text
asField sep = T.intercalate sep . T.words