1 -----------------------------------------------------------------------------
2 -- $Id: Main.hs,v 1.3 2000/12/28 10:34:56 qrczak Exp $
4 -- (originally "GlueHsc.hs" by Marcin 'Qrczak' Kowalczyk)
6 -- Program for converting .hsc files to .hs files, by converting the
7 -- file into a C program which is run to generate the Haskell source.
8 -- Certain items known only to the C compiler can then be used in
9 -- the Haskell module; for example #defined constants, byte offsets
10 -- within structures, etc.
12 -- See the documentation in the Users' Guide for more details.
15 import System (getProgName, getArgs, ExitCode(..), system, exitWith, exitFailure)
16 import Directory (removeFile)
18 import Monad (liftM, liftM2, when)
19 import Char (ord, intToDigit, isSpace, isAlphaNum, toUpper)
20 import List (intersperse)
30 options :: [OptDescr Flag]
32 Option "t" ["template"] (ReqArg Template "FILE") "template file",
33 Option "" ["cc"] (ReqArg Compiler "PROG") "C compiler to use",
34 Option "" ["ld"] (ReqArg Linker "PROG") "linker to use",
35 Option "" ["cflag"] (ReqArg CompFlag "FLAG") "flag to pass to the C compiler",
36 Option "" ["lflag"] (ReqArg LinkFlag "FLAG") "flag to pass to the linker",
37 Option "" ["help"] (NoArg Help) "display this help and exit"]
42 let header = "Usage: "++prog++" [OPTIONS...] INPUT.hsc [...]"
44 case getOpt Permute options args of
45 (flags, _, _) | any isHelp flags -> putStrLn (usageInfo header options)
46 where isHelp Help = True; isHelp _ = False
47 (_, [], []) -> putStrLn (prog++": No input files")
48 (flags, files, []) -> mapM_ (processFile flags) files
51 putStrLn (usageInfo header options)
54 processFile :: [Flag] -> String -> IO ()
55 processFile flags name = do
56 parsed <- parseFromFile parser name
59 Right toks -> output flags name toks
63 | Special String String
65 parser :: Parser [Token]
66 parser = many (text <|> special)
69 text = liftM Text $ many1 $ satisfy (/= '#') <|> try (string "##" >> return '#')
71 special :: Parser Token
74 skipMany (oneOf " \t")
75 key <- liftM2 (:) (letter <|> char '_') (many (alphaNum <|> char '_'))
77 skipMany (oneOf " \t")
79 return (Special key arg)
81 argument :: Parser String -> Parser String
82 argument eol = liftM concat $ many
83 ( many1 (noneOf "\n\"\'()/[\\]{}")
85 <|> (do char '\"'; a <- cString '\''; char '\"'; return ("\""++a++"\""))
86 <|> (do char '\''; a <- cString '\"'; char '\''; return ("\'"++a++"\'"))
87 <|> (do char '('; a <- nested; char ')'; return ("("++a++")"))
88 <|> (do try (string "/*"); comment; return " ")
89 <|> (do try (string "//"); skipMany (satisfy (/= '\n')); return " ")
91 <|> (do char '['; a <- nested; char ']'; return ("["++a++"]"))
92 <|> (do char '\\'; a <- anyChar; return ['\\',a])
93 <|> (do char '{'; a <- nested; char '}'; return ("{"++a++"}"))
95 where nested = argument (string "\n")
98 comment = (do skipMany1 (noneOf "*"); comment)
99 <|> (do try (string "*/"); return ())
100 <|> (do char '*'; comment)
103 cString :: Char -> Parser String
104 cString otherQuote = liftM concat $ many
105 ( many1 (noneOf "\n\\\'\"")
106 <|> string [otherQuote]
107 <|> (do char '\\'; a <- anyChar; return ['\\',a])
108 <?> "C character or string")
110 output :: [Flag] -> String -> [Token] -> IO ()
111 output flags name toks = let
112 baseName = case reverse name of
113 'c':base -> reverse base
115 cProgName = baseName++"c_make_hs.c"
116 oProgName = baseName++"c_make_hs.o"
117 progName = baseName++"c_make_hs"
119 outHName = baseName++".h"
120 outCName = baseName++".c"
122 execProgName = case progName of
126 specials = [(key, arg) | Special key arg <- toks]
128 needsC = any (\(key, _) -> key == "def") specials
131 includeGuard = map fixChar outHName
133 fixChar c | isAlphaNum c = toUpper c
138 compiler <- case [c | Compiler c <- flags] of
141 _ -> onlyOne "compiler"
142 linker <- case [l | Linker l <- flags] of
145 _ -> onlyOne "linker"
147 writeFile cProgName $
148 concat ["#include \""++t++"\"\n" | Template t <- flags] ++
149 outHeaderCProg specials ++
150 "\nint main (void)\n{\n" ++
151 outHeaderHs (if needsH then Just outHName else Nothing) specials ++
152 concatMap outTokenHs toks ++
155 compilerStatus <- system $
158 concat [" "++f | CompFlag f <- flags]++
161 case compilerStatus of
162 e@(ExitFailure _) -> exitWith e
166 linkerStatus <- system $
168 concat [" "++f | LinkFlag f <- flags]++
172 e@(ExitFailure _) -> exitWith e
176 system (execProgName++" >"++outHsName)
179 when needsH $ writeFile outHName $
180 "#ifndef "++includeGuard++"\n\
181 \#define "++includeGuard++"\n\
182 \#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
185 \#include <HsFFI.h>\n"++
186 concatMap outTokenH specials++
189 when needsC $ writeFile outCName $
190 "#include \""++outHName++"\"\n"++
191 concatMap outTokenC specials
193 onlyOne :: String -> IO a
195 putStrLn ("Only one "++what++" may be specified")
198 outHeaderCProg :: [(String, String)] -> String
199 outHeaderCProg = concatMap $ \(key, arg) -> case key of
200 "include" -> "#include "++arg++"\n"
201 "define" -> "#define "++arg++"\n"
202 "undef" -> "#undef "++arg++"\n"
204 's':'t':'r':'u':'c':'t':' ':_ -> arg++"\n"
205 't':'y':'p':'e':'d':'e':'f':' ':_ -> arg++"\n"
207 _ | conditional key -> "#"++key++" "++arg++"\n"
208 "let" -> case break (== '=') arg of
210 (header, _:body) -> case break isSpace header of
212 "#define hsc_"++name++"("++dropWhile isSpace args++") \
213 \printf ("++joinLines body++");\n"
216 joinLines = concat . intersperse " \\\n" . lines
218 outHeaderHs :: Maybe String -> [(String, String)] -> String
219 outHeaderHs inH toks =
220 " hsc_begin_options();\n"++
221 concatMap outSpecial toks ++
223 " hsc_end_options();\n\n"
225 outSpecial (key, arg) = case key of
226 "include" -> case inH of
227 Nothing -> outOption ("-#include "++arg)
229 "define" -> case inH of
230 Nothing | goodForOptD arg -> outOption ("-optc-D"++toOptD arg)
232 "option" -> outOption arg
233 _ | conditional key -> "#"++key++" "++arg++"\n"
235 goodForOptD arg = case arg of
237 c:_ | isSpace c -> True
240 toOptD arg = case break isSpace arg of
242 (name, _:value) -> name++'=':dropWhile isSpace value
243 includeH = case inH of
245 Just name -> outOption ("-#include \""++name++"\"")
246 outOption s = " hsc_option (\""++showCString s++"\");\n"
248 outTokenHs :: Token -> String
249 outTokenHs (Text s) = " fputs (\""++showCString s++"\", stdout);\n"
250 outTokenHs (Special key arg) = case key of
256 _ | conditional key -> "#"++key++" "++arg++"\n"
258 _ -> " hsc_"++key++" ("++arg++");\n"
260 outTokenH :: (String, String) -> String
261 outTokenH (key, arg) = case key of
262 "include" -> "#include "++arg++"\n"
263 "define" -> "#define " ++arg++"\n"
264 "undef" -> "#undef " ++arg++"\n"
266 's':'t':'r':'u':'c':'t':' ':_ -> arg++"\n"
267 't':'y':'p':'e':'d':'e':'f':' ':_ -> arg++"\n"
268 'i':'n':'l':'i':'n':'e':' ':_ ->
273 _ -> "extern "++header++";\n"
274 where header = takeWhile (\c -> c/='{' && c/='=') arg
275 _ | conditional key -> "#"++key++" "++arg++"\n"
278 outTokenC :: (String, String) -> String
279 outTokenC (key, arg) = case key of
281 's':'t':'r':'u':'c':'t':' ':_ -> ""
282 't':'y':'p':'e':'d':'e':'f':' ':_ -> ""
283 'i':'n':'l':'i':'n':'e':' ':_ ->
288 "\n#ifndef __GNUC__\n\
294 where (header, body) = span (\c -> c/='{' && c/='=') arg
295 _ | conditional key -> "#"++key++" "++arg++"\n"
298 conditional :: String -> Bool
299 conditional "if" = True
300 conditional "ifdef" = True
301 conditional "ifndef" = True
302 conditional "elif" = True
303 conditional "else" = True
304 conditional "endif" = True
305 conditional "error" = True
306 conditional _ = False
308 showCString :: String -> String
309 showCString = concatMap showCChar
311 showCChar '\"' = "\\\""
312 showCChar '\'' = "\\\'"
313 showCChar '?' = "\\?"
314 showCChar '\\' = "\\\\"
315 showCChar c | c >= ' ' && c <= '~' = [c]
316 showCChar '\a' = "\\a"
317 showCChar '\b' = "\\b"
318 showCChar '\f' = "\\f"
319 showCChar '\n' = "\\n\"\n \""
320 showCChar '\r' = "\\r"
321 showCChar '\t' = "\\t"
322 showCChar '\v' = "\\v"
324 intToDigit (ord c `quot` 64),
325 intToDigit (ord c `quot` 8 `mod` 8),
326 intToDigit (ord c `mod` 8)]