3 -----------------------------------------------------------------------------
4 -- $Id: Main.hs,v 1.20 2001/02/13 16:11:27 rrt Exp $
6 -- (originally "GlueHsc.hs" by Marcin 'Qrczak' Kowalczyk)
8 -- Program for converting .hsc files to .hs files, by converting the
9 -- file into a C program which is run to generate the Haskell source.
10 -- Certain items known only to the C compiler can then be used in
11 -- the Haskell module; for example #defined constants, byte offsets
12 -- within structures, etc.
14 -- See the documentation in the Users' Guide for more details.
16 #include "../../includes/config.h"
19 import System (getProgName, getArgs, ExitCode(..), exitWith, exitFailure)
20 import KludgedSystem (system, defaultCompiler)
21 import Directory (removeFile)
24 import Monad (liftM, liftM2, when)
25 import Char (ord, intToDigit, isSpace, isAlpha, isAlphaNum, toUpper)
26 import List (intersperse)
29 version = "hsc2hs-0.64"
41 include :: String -> Flag
42 include s@('\"':_) = Include s
43 include s@('<' :_) = Include s
44 include s = Include ("\""++s++"\"")
46 options :: [OptDescr Flag]
48 Option "t" ["template"] (ReqArg Template "FILE") "template file",
49 Option "" ["cc"] (ReqArg Compiler "PROG") "C compiler to use",
50 Option "" ["ld"] (ReqArg Linker "PROG") "linker to use",
51 Option "" ["cflag"] (ReqArg CompFlag "FLAG") "flag to pass to the C compiler",
52 Option "I" [] (ReqArg (CompFlag . ("-I"++))
53 "DIR") "passed to the C compiler",
54 Option "" ["lflag"] (ReqArg LinkFlag "FLAG") "flag to pass to the linker",
55 Option "" ["include"] (ReqArg include "FILE") "as if placed in the source",
56 Option "" ["help"] (NoArg Help) "display this help and exit",
57 Option "" ["version"] (NoArg Version) "output version information and exit"]
62 let header = "Usage: "++prog++" [OPTIONS] INPUT.hsc [...]"
64 case getOpt Permute options args of
66 | any isHelp flags -> putStrLn (usageInfo header options)
67 | any isVersion flags -> putStrLn version
69 isHelp Help = True; isHelp _ = False
70 isVersion Version = True; isVersion _ = False
71 (_, [], []) -> putStrLn (prog++": No input files")
72 (flags, files, []) -> mapM_ (processFile flags) files
75 putStrLn (usageInfo header options)
78 processFile :: [Flag] -> String -> IO ()
79 processFile flags name = do
80 parsed <- parseFromFile parser name
82 Left err -> do print err; exitFailure
83 Right toks -> output flags name toks
86 = Text SourcePos String
87 | Special SourcePos String String
89 parser :: Parser [Token]
90 parser = many (text <|> special)
95 liftM (Text pos . concat) $ many1
96 ( many1 (satisfy (\ch -> not (isAlpha ch || ch `elem` "\"#'-_{")))
97 <|> (do a <- satisfy (\ch -> isAlpha ch || ch == '_')
98 b <- many (satisfy (\ch -> isAlphaNum ch || ch == '_' || ch == '\''))
100 <|> (do char '\"'; a <- hsString '\"'; char '\"'; return ("\""++a++"\""))
101 <|> (do try (string "##"); return "#")
102 <|> (do char '\''; a <- hsString '\''; char '\''; return ("\'"++a++"\'"))
103 <|> (do try (string "--"); a <- many (satisfy (/= '\n')); return ("--"++a))
105 <|> (do try (string "{-#"); optional (try linePragma); a <- hsComment; return ("{-#"++a))
106 <|> (do try (string "{-"); a <- hsComment; return ("{-"++a))
108 <?> "Haskell source")
110 linePragma :: Parser ()
119 file <- many (satisfy (/= '\"'))
124 setPosition (newPos file (read line - 1) 1)
126 hsComment :: Parser String
128 ( (do a <- many1 (noneOf "-{"); b <- hsComment; return (a++b))
129 <|> try (string "-}")
130 <|> (do char '-'; b <- hsComment; return ('-':b))
131 <|> (do try (string "{-"); a <- hsComment; b <- hsComment; return ("{-"++a++b))
132 <|> (do char '{'; b <- hsComment; return ('{':b))
133 <?> "Haskell comment")
135 hsString :: Char -> Parser String
138 ( many1 (noneOf (quote:"\n\\"))
139 <|> (do char '\\'; a <- escape; return ('\\':a))
140 <?> "Haskell character or string")
142 escape = (do a <- many1 (satisfy isSpace); char '\\'; return (a++"\\"))
143 <|> (do a <- anyChar; return [a])
145 special :: Parser Token
149 skipMany (oneOf " \t")
150 key <- liftM2 (:) (letter <|> char '_') (many (alphaNum <|> char '_'))
152 skipMany (oneOf " \t")
153 arg <- argument pzero
154 return (Special pos key arg)
156 argument :: Parser String -> Parser String
159 ( many1 (noneOf "\n\"\'()/[\\]{}")
161 <|> (do char '\"'; a <- cString '\"'; char '\"'; return ("\""++a++"\""))
162 <|> (do char '\''; a <- cString '\''; char '\''; return ("\'"++a++"\'"))
163 <|> (do char '('; a <- nested; char ')'; return ("("++a++")"))
164 <|> (do try (string "/*"); cComment; return " ")
165 <|> (do try (string "//"); skipMany (satisfy (/= '\n')); return " ")
167 <|> (do char '['; a <- nested; char ']'; return ("["++a++"]"))
168 <|> (do char '\\'; a <- anyChar; return $ if a == '\n' then [] else ['\\',a])
169 <|> (do char '{'; a <- nested; char '}'; return ("{"++a++"}"))
171 where nested = argument (string "\n")
173 cComment :: Parser ()
175 ( (do skipMany1 (noneOf "*"); cComment)
176 <|> (do try (string "*/"); return ())
177 <|> (do char '*'; cComment)
180 cString :: Char -> Parser String
183 ( many1 (noneOf (quote:"\n\\"))
184 <|> (do char '\\'; a <- anyChar; return ['\\',a])
185 <?> "C character or string")
187 output :: [Flag] -> String -> [Token] -> IO ()
188 output flags name toks = let
189 baseName = case reverse name of
190 'c':base -> reverse base
192 cProgName = baseName++"c_make_hs.c"
193 oProgName = baseName++"c_make_hs.o"
194 progName = baseName++"c_make_hs"
196 outHName = baseName++".h"
197 outCName = baseName++".c"
199 execProgName = case progName of
203 specials = [(pos, key, arg) | Special pos key arg <- toks]
205 needsC = any (\(_, key, _) -> key == "def") specials
208 includeGuard = map fixChar outHName
210 fixChar c | isAlphaNum c = toUpper c
215 compiler <- case [c | Compiler c <- flags] of
218 _ -> onlyOne "compiler"
219 linker <- case [l | Linker l <- flags] of
220 [] -> return defaultCompiler
222 _ -> onlyOne "linker"
224 writeFile cProgName $
225 concat ["#include \""++t++"\"\n" | Template t <- flags]++
226 concat ["#include "++f++"\n" | Include f <- flags]++
227 outHeaderCProg specials++
228 "\nint main (void)\n{\n"++
229 outHeaderHs flags (if needsH then Just outHName else Nothing) specials++
230 outHsLine (newPos name 0 1)++
231 concatMap outTokenHs toks++
234 compilerStatus <- system $
237 concat [" "++f | CompFlag f <- flags]++
240 case compilerStatus of
241 e@(ExitFailure _) -> exitWith e
245 linkerStatus <- system $
247 concat [" "++f | LinkFlag f <- flags]++
251 e@(ExitFailure _) -> exitWith e
255 system (execProgName++" >"++outHsName)
258 when needsH $ writeFile outHName $
259 "#ifndef "++includeGuard++"\n\
260 \#define "++includeGuard++"\n\
261 \#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
264 \#include <HsFFI.h>\n\
267 \#define HsChar int\n\
269 concat ["#include "++n++"\n" | Include n <- flags]++
270 concatMap outTokenH specials++
273 when needsC $ writeFile outCName $
274 "#include \""++outHName++"\"\n"++
275 concatMap outTokenC specials
277 onlyOne :: String -> IO a
279 putStrLn ("Only one "++what++" may be specified")
282 outHeaderCProg :: [(SourcePos, String, String)] -> String
284 concatMap $ \(pos, key, arg) -> case key of
285 "include" -> outCLine pos++"#include "++arg++"\n"
286 "define" -> outCLine pos++"#define "++arg++"\n"
287 "undef" -> outCLine pos++"#undef "++arg++"\n"
289 's':'t':'r':'u':'c':'t':' ':_ -> outCLine pos++arg++"\n"
290 't':'y':'p':'e':'d':'e':'f':' ':_ -> outCLine pos++arg++"\n"
292 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
293 "let" -> case break (== '=') arg of
295 (header, _:body) -> case break isSpace header of
298 "#define hsc_"++name++"("++dropWhile isSpace args++") \
299 \printf ("++joinLines body++");\n"
302 joinLines = concat . intersperse " \\\n" . lines
304 outHeaderHs :: [Flag] -> Maybe String -> [(SourcePos, String, String)] -> String
305 outHeaderHs flags inH toks =
306 "#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
307 \ printf (\"{-# OPTIONS -optc-D__GLASGOW_HASKELL__=%d #-}\\n\", \
308 \__GLASGOW_HASKELL__);\n\
311 concatMap outSpecial toks
313 outSpecial (pos, key, arg) = case key of
314 "include" -> case inH of
315 Nothing -> outOption ("-#include "++arg)
317 "define" -> case inH of
318 Nothing | goodForOptD arg -> outOption ("-optc-D"++toOptD arg)
320 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
322 goodForOptD arg = case arg of
324 c:_ | isSpace c -> True
327 toOptD arg = case break isSpace arg of
329 (name, _:value) -> name++'=':dropWhile isSpace value
331 outOption ("-#include "++name++"")
332 | name <- case inH of
333 Nothing -> [name | Include name <- flags]
334 Just name -> ["\""++name++"\""]]
335 outOption s = " printf (\"{-# OPTIONS %s #-}\\n\", \""++
336 showCString s++"\");\n"
338 outTokenHs :: Token -> String
339 outTokenHs (Text pos text) =
340 case break (== '\n') text of
341 (all, []) -> outText all
343 outText (first++"\n")++
347 outText s = " fputs (\""++showCString s++"\", stdout);\n"
348 outTokenHs (Special pos key arg) =
354 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
356 "enum" -> outCLine pos++outEnum arg
357 _ -> outCLine pos++" hsc_"++key++" ("++arg++");\n"
359 outEnum :: String -> String
361 case break (== ',') arg of
363 (t, _:afterT) -> case break (== ',') afterT of
366 enums (_:s) = case break (== ',') s of
368 this = case break (== '=') $ dropWhile isSpace enum of
370 " hsc_enum ("++t++", "++f++", \
371 \hsc_haskellize (\""++name++"\"), "++
374 " hsc_enum ("++t++", "++f++", \
375 \printf (\"%s\", \""++hsName++"\"), "++
380 outTokenH :: (SourcePos, String, String) -> String
381 outTokenH (pos, key, arg) =
383 "include" -> outCLine pos++"#include "++arg++"\n"
384 "define" -> outCLine pos++"#define " ++arg++"\n"
385 "undef" -> outCLine pos++"#undef " ++arg++"\n"
386 "def" -> outCLine pos++case arg of
387 's':'t':'r':'u':'c':'t':' ':_ -> arg++"\n"
388 't':'y':'p':'e':'d':'e':'f':' ':_ -> arg++"\n"
389 'i':'n':'l':'i':'n':'e':' ':_ ->
394 _ -> "extern "++header++";\n"
395 where header = takeWhile (\c -> c /= '{' && c /= '=') arg
396 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
399 outTokenC :: (SourcePos, String, String) -> String
400 outTokenC (pos, key, arg) =
403 's':'t':'r':'u':'c':'t':' ':_ -> ""
404 't':'y':'p':'e':'d':'e':'f':' ':_ -> ""
405 'i':'n':'l':'i':'n':'e':' ':_ ->
411 "\n#ifndef __GNUC__\n\
416 _ -> outCLine pos++arg++"\n"
417 where (header, body) = span (\c -> c /= '{' && c /= '=') arg
418 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
421 conditional :: String -> Bool
422 conditional "if" = True
423 conditional "ifdef" = True
424 conditional "ifndef" = True
425 conditional "elif" = True
426 conditional "else" = True
427 conditional "endif" = True
428 conditional "error" = True
429 conditional "warning" = True
430 conditional _ = False
432 sourceFileName :: SourcePos -> String
433 sourceFileName pos = fileName (sourceName pos)
435 fileName s = case break (== '/') s of
437 (_, _:rest) -> fileName rest
439 outCLine :: SourcePos -> String
441 "# "++show (sourceLine pos)++
442 " \""++showCString (sourceFileName pos)++"\"\n"
444 outHsLine :: SourcePos -> String
447 show (sourceLine pos + 1)++", \""++
448 showCString (sourceFileName pos)++"\");\n"
450 showCString :: String -> String
451 showCString = concatMap showCChar
453 showCChar '\"' = "\\\""
454 showCChar '\'' = "\\\'"
455 showCChar '?' = "\\?"
456 showCChar '\\' = "\\\\"
457 showCChar c | c >= ' ' && c <= '~' = [c]
458 showCChar '\a' = "\\a"
459 showCChar '\b' = "\\b"
460 showCChar '\f' = "\\f"
461 showCChar '\n' = "\\n\"\n \""
462 showCChar '\r' = "\\r"
463 showCChar '\t' = "\\t"
464 showCChar '\v' = "\\v"
466 intToDigit (ord c `quot` 64),
467 intToDigit (ord c `quot` 8 `mod` 8),
468 intToDigit (ord c `mod` 8)]