1 -----------------------------------------------------------------------------
2 -- $Id: Main.hs,v 1.24 2001/03/04 11:18:03 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(..), exitWith, exitFailure)
16 import KludgedSystem (system, defaultCompiler)
17 import Directory (removeFile)
20 import Monad (liftM, liftM2, when)
21 import Char (ord, intToDigit, isSpace, isAlpha, isAlphaNum, toUpper)
22 import List (intersperse)
25 version = "hsc2hs-0.65"
38 include :: String -> Flag
39 include s@('\"':_) = Include s
40 include s@('<' :_) = Include s
41 include s = Include ("\""++s++"\"")
43 options :: [OptDescr Flag]
45 Option "t" ["template"] (ReqArg Template "FILE") "template file",
46 Option "c" ["cc"] (ReqArg Compiler "PROG") "C compiler to use",
47 Option "l" ["ld"] (ReqArg Linker "PROG") "linker to use",
48 Option "C" ["cflag"] (ReqArg CompFlag "FLAG") "flag to pass to the C compiler",
49 Option "I" [] (ReqArg (CompFlag . ("-I"++))
50 "DIR") "passed to the C compiler",
51 Option "L" ["lflag"] (ReqArg LinkFlag "FLAG") "flag to pass to the linker",
52 Option "" ["keep"] (NoArg Keep) "don't delete *.hs_make.c",
53 Option "i" ["include"] (ReqArg include "FILE") "as if placed in the source",
54 Option "" ["help"] (NoArg Help) "display this help and exit",
55 Option "" ["version"] (NoArg Version) "output version information and exit"]
60 let header = "Usage: "++prog++" [OPTIONS] INPUT.hsc [...]"
62 case getOpt Permute options args of
64 | any isHelp flags -> putStrLn (usageInfo header options)
65 | any isVersion flags -> putStrLn version
67 isHelp Help = True; isHelp _ = False
68 isVersion Version = True; isVersion _ = False
69 (_, [], []) -> putStrLn (prog++": No input files")
70 (flags, files, []) -> mapM_ (processFile flags) files
73 putStrLn (usageInfo header options)
76 processFile :: [Flag] -> String -> IO ()
77 processFile flags name = do
78 parsed <- parseFromFile parser name
80 Left err -> do print err; exitFailure
81 Right toks -> output flags name toks
84 = Text SourcePos String
85 | Special SourcePos String String
87 parser :: Parser [Token]
88 parser = many (text <|> special)
93 liftM (Text pos . concat) $ many1
94 ( many1 (satisfy (\ch -> not (isAlpha ch || ch `elem` "\"#'-_{")))
95 <|> (do a <- satisfy (\ch -> isAlpha ch || ch == '_')
96 b <- many (satisfy (\ch -> isAlphaNum ch || ch == '_' || ch == '\''))
98 <|> (do char '\"'; a <- hsString '\"'; char '\"'; return ("\""++a++"\""))
99 <|> (do try (string "##"); return "#")
100 <|> (do char '\''; a <- hsString '\''; char '\''; return ("\'"++a++"\'"))
101 <|> (do try (string "--"); a <- many (satisfy (/= '\n')); return ("--"++a))
103 <|> (do try (string "{-#"); optional (try linePragma); a <- hsComment; return ("{-#"++a))
104 <|> (do try (string "{-"); a <- hsComment; return ("{-"++a))
106 <?> "Haskell source")
108 linePragma :: Parser ()
117 file <- many (satisfy (/= '\"'))
122 setPosition (newPos file (read line - 1) 1)
124 hsComment :: Parser String
126 ( (do a <- many1 (noneOf "-{"); b <- hsComment; return (a++b))
127 <|> try (string "-}")
128 <|> (do char '-'; b <- hsComment; return ('-':b))
129 <|> (do try (string "{-"); a <- hsComment; b <- hsComment; return ("{-"++a++b))
130 <|> (do char '{'; b <- hsComment; return ('{':b))
131 <?> "Haskell comment")
133 hsString :: Char -> Parser String
136 ( many1 (noneOf (quote:"\n\\"))
137 <|> (do char '\\'; a <- escape; return ('\\':a))
138 <?> "Haskell character or string")
140 escape = (do a <- many1 (satisfy isSpace); char '\\'; return (a++"\\"))
141 <|> (do a <- anyChar; return [a])
143 special :: Parser Token
147 skipMany (oneOf " \t")
148 keyArg pos pzero <|> do
150 skipMany (oneOf " \t")
151 sp <- keyArg pos (string "\n")
155 keyArg :: SourcePos -> Parser String -> Parser Token
157 key <- liftM2 (:) (letter <|> char '_') (many (alphaNum <|> char '_'))
159 skipMany (oneOf " \t")
161 return (Special pos key arg)
163 argument :: Parser String -> Parser String
166 ( many1 (noneOf "\n\"\'()/[\\]{}")
168 <|> (do char '\"'; a <- cString '\"'; char '\"'; return ("\""++a++"\""))
169 <|> (do char '\''; a <- cString '\''; char '\''; return ("\'"++a++"\'"))
170 <|> (do char '('; a <- nested; char ')'; return ("("++a++")"))
171 <|> (do try (string "/*"); cComment; return " ")
172 <|> (do try (string "//"); skipMany (satisfy (/= '\n')); return " ")
174 <|> (do char '['; a <- nested; char ']'; return ("["++a++"]"))
175 <|> (do char '\\'; a <- anyChar; return $ if a == '\n' then [] else ['\\',a])
176 <|> (do char '{'; a <- nested; char '}'; return ("{"++a++"}"))
178 where nested = argument (string "\n")
180 cComment :: Parser ()
182 ( (do skipMany1 (noneOf "*"); cComment)
183 <|> (do try (string "*/"); return ())
184 <|> (do char '*'; cComment)
187 cString :: Char -> Parser String
190 ( many1 (noneOf (quote:"\n\\"))
191 <|> (do char '\\'; a <- anyChar; return ['\\',a])
192 <?> "C character or string")
194 output :: [Flag] -> String -> [Token] -> IO ()
195 output flags name toks = let
196 baseName = case reverse name of
197 'c':base -> reverse base
199 cProgName = baseName++"_make.c"
200 oProgName = baseName++"_make.o"
201 progName = baseName++"_make"
203 outHName = baseName++".h"
204 outCName = baseName++".c"
206 execProgName = case progName of
210 specials = [(pos, key, arg) | Special pos key arg <- toks]
212 needsC = any (\(_, key, _) -> key == "def") specials
215 includeGuard = map fixChar outHName
217 fixChar c | isAlphaNum c = toUpper c
222 compiler <- case [c | Compiler c <- flags] of
225 _ -> onlyOne "compiler"
226 linker <- case [l | Linker l <- flags] of
227 [] -> return defaultCompiler
229 _ -> onlyOne "linker"
231 writeFile cProgName $
232 concat ["#include \""++t++"\"\n" | Template t <- flags]++
233 concat ["#include "++f++"\n" | Include f <- flags]++
234 outHeaderCProg specials++
235 "\nint main (void)\n{\n"++
236 outHeaderHs flags (if needsH then Just outHName else Nothing) specials++
237 outHsLine (newPos name 0 1)++
238 concatMap outTokenHs toks++
241 compilerStatus <- system $
244 concat [" "++f | CompFlag f <- flags]++
247 case compilerStatus of
248 e@(ExitFailure _) -> exitWith e
250 when (null [() | Keep <- flags]) $ removeFile cProgName
252 linkerStatus <- system $
254 concat [" "++f | LinkFlag f <- flags]++
258 e@(ExitFailure _) -> exitWith e
262 system (execProgName++" >"++outHsName)
265 when needsH $ writeFile outHName $
266 "#ifndef "++includeGuard++"\n\
267 \#define "++includeGuard++"\n\
268 \#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
271 \#include <HsFFI.h>\n\
274 \#define HsChar int\n\
276 concat ["#include "++n++"\n" | Include n <- flags]++
277 concatMap outTokenH specials++
280 when needsC $ writeFile outCName $
281 "#include \""++outHName++"\"\n"++
282 concatMap outTokenC specials
284 onlyOne :: String -> IO a
286 putStrLn ("Only one "++what++" may be specified")
289 outHeaderCProg :: [(SourcePos, String, String)] -> String
291 concatMap $ \(pos, key, arg) -> case key of
292 "include" -> outCLine pos++"#include "++arg++"\n"
293 "define" -> outCLine pos++"#define "++arg++"\n"
294 "undef" -> outCLine pos++"#undef "++arg++"\n"
296 's':'t':'r':'u':'c':'t':' ':_ -> outCLine pos++arg++"\n"
297 't':'y':'p':'e':'d':'e':'f':' ':_ -> outCLine pos++arg++"\n"
299 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
300 "let" -> case break (== '=') arg of
302 (header, _:body) -> case break isSpace header of
305 "#define hsc_"++name++"("++dropWhile isSpace args++") \
306 \printf ("++joinLines body++");\n"
309 joinLines = concat . intersperse " \\\n" . lines
311 outHeaderHs :: [Flag] -> Maybe String -> [(SourcePos, String, String)] -> String
312 outHeaderHs flags inH toks =
313 "#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
314 \ printf (\"{-# OPTIONS -optc-D__GLASGOW_HASKELL__=%d #-}\\n\", \
315 \__GLASGOW_HASKELL__);\n\
318 concatMap outSpecial toks
320 outSpecial (pos, key, arg) = case key of
321 "include" -> case inH of
322 Nothing -> outOption ("-#include "++arg)
324 "define" -> case inH of
325 Nothing | goodForOptD arg -> outOption ("-optc-D"++toOptD arg)
327 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
329 goodForOptD arg = case arg of
331 c:_ | isSpace c -> True
334 toOptD arg = case break isSpace arg of
336 (name, _:value) -> name++'=':dropWhile isSpace value
338 outOption ("-#include "++name++"")
339 | name <- case inH of
340 Nothing -> [name | Include name <- flags]
341 Just name -> ["\""++name++"\""]]
342 outOption s = " printf (\"{-# OPTIONS %s #-}\\n\", \""++
343 showCString s++"\");\n"
345 outTokenHs :: Token -> String
346 outTokenHs (Text pos text) =
347 case break (== '\n') text of
348 (all, []) -> outText all
350 outText (first++"\n")++
354 outText s = " fputs (\""++showCString s++"\", stdout);\n"
355 outTokenHs (Special pos key arg) =
361 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
363 "enum" -> outCLine pos++outEnum arg
364 _ -> outCLine pos++" hsc_"++key++" ("++arg++");\n"
366 outEnum :: String -> String
368 case break (== ',') arg of
370 (t, _:afterT) -> case break (== ',') afterT of
373 enums (_:s) = case break (== ',') s of
375 this = case break (== '=') $ dropWhile isSpace enum of
377 " hsc_enum ("++t++", "++f++", \
378 \hsc_haskellize (\""++name++"\"), "++
381 " hsc_enum ("++t++", "++f++", \
382 \printf (\"%s\", \""++hsName++"\"), "++
387 outTokenH :: (SourcePos, String, String) -> String
388 outTokenH (pos, key, arg) =
390 "include" -> outCLine pos++"#include "++arg++"\n"
391 "define" -> outCLine pos++"#define " ++arg++"\n"
392 "undef" -> outCLine pos++"#undef " ++arg++"\n"
393 "def" -> outCLine pos++case arg of
394 's':'t':'r':'u':'c':'t':' ':_ -> arg++"\n"
395 't':'y':'p':'e':'d':'e':'f':' ':_ -> arg++"\n"
396 'i':'n':'l':'i':'n':'e':' ':_ ->
401 _ -> "extern "++header++";\n"
402 where header = takeWhile (\c -> c /= '{' && c /= '=') arg
403 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
406 outTokenC :: (SourcePos, String, String) -> String
407 outTokenC (pos, key, arg) =
410 's':'t':'r':'u':'c':'t':' ':_ -> ""
411 't':'y':'p':'e':'d':'e':'f':' ':_ -> ""
412 'i':'n':'l':'i':'n':'e':' ':_ ->
418 "\n#ifndef __GNUC__\n\
423 _ -> outCLine pos++arg++"\n"
424 where (header, body) = span (\c -> c /= '{' && c /= '=') arg
425 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
428 conditional :: String -> Bool
429 conditional "if" = True
430 conditional "ifdef" = True
431 conditional "ifndef" = True
432 conditional "elif" = True
433 conditional "else" = True
434 conditional "endif" = True
435 conditional "error" = True
436 conditional "warning" = True
437 conditional _ = False
439 sourceFileName :: SourcePos -> String
440 sourceFileName pos = fileName (sourceName pos)
442 fileName s = case break (== '/') s of
444 (_, _:rest) -> fileName rest
446 outCLine :: SourcePos -> String
448 "# "++show (sourceLine pos)++
449 " \""++showCString (sourceFileName pos)++"\"\n"
451 outHsLine :: SourcePos -> String
454 show (sourceLine pos + 1)++", \""++
455 showCString (sourceFileName pos)++"\");\n"
457 showCString :: String -> String
458 showCString = concatMap showCChar
460 showCChar '\"' = "\\\""
461 showCChar '\'' = "\\\'"
462 showCChar '?' = "\\?"
463 showCChar '\\' = "\\\\"
464 showCChar c | c >= ' ' && c <= '~' = [c]
465 showCChar '\a' = "\\a"
466 showCChar '\b' = "\\b"
467 showCChar '\f' = "\\f"
468 showCChar '\n' = "\\n\"\n \""
469 showCChar '\r' = "\\r"
470 showCChar '\t' = "\\t"
471 showCChar '\v' = "\\v"
473 intToDigit (ord c `quot` 64),
474 intToDigit (ord c `quot` 8 `mod` 8),
475 intToDigit (ord c `mod` 8)]