1 ------------------------------------------------------------------------
2 -- $Id: Main.hs,v 1.28 2001/03/29 08:03:21 qrczak Exp $
4 -- Program for converting .hsc files to .hs files, by converting the
5 -- file into a C program which is run to generate the Haskell source.
6 -- Certain items known only to the C compiler can then be used in
7 -- the Haskell module; for example #defined constants, byte offsets
8 -- within structures, etc.
10 -- See the documentation in the Users' Guide for more details.
13 import System (getProgName, getArgs, ExitCode(..), exitWith, exitFailure)
14 import KludgedSystem (system, defaultCompiler)
15 import Directory (removeFile)
16 import Monad (MonadPlus(..), liftM, liftM2, when, unless)
17 import Char (isAlpha, isAlphaNum, isSpace, isDigit, toUpper, intToDigit, ord)
18 import List (intersperse)
21 version = "hsc2hs-0.65"
33 | Define String (Maybe String)
37 include :: String -> Flag
38 include s@('\"':_) = Include s
39 include s@('<' :_) = Include s
40 include s = Include ("\""++s++"\"")
42 define :: String -> Flag
43 define s = case break (== '=') s of
44 (name, []) -> Define name Nothing
45 (name, _:value) -> Define name (Just value)
47 options :: [OptDescr Flag]
49 Option "t" ["template"] (ReqArg Template "FILE") "template file",
50 Option "c" ["cc"] (ReqArg Compiler "PROG") "C compiler to use",
51 Option "l" ["ld"] (ReqArg Linker "PROG") "linker to use",
52 Option "C" ["cflag"] (ReqArg CompFlag "FLAG") "flag to pass to the C compiler",
53 Option "I" [] (ReqArg (CompFlag . ("-I"++))
54 "DIR") "passed to the C compiler",
55 Option "L" ["lflag"] (ReqArg LinkFlag "FLAG") "flag to pass to the linker",
56 Option "" ["no-compile"] (NoArg NoCompile) "stop after writing *_make.c",
57 Option "i" ["include"] (ReqArg include "FILE") "as if placed in the source",
58 Option "D" ["define"] (ReqArg define "NAME[=VALUE]") "as if placed in the source",
59 Option "o" ["output"] (ReqArg Output "FILE") "name of main output file",
60 Option "s" ["support"] (ReqArg Support "FILE") "basename of support output files (with .h, .c removed)",
61 Option "" ["help"] (NoArg Help) "display this help and exit",
62 Option "" ["version"] (NoArg Version) "output version information and exit"]
67 let header = "Usage: "++prog++" [OPTIONS] INPUT.hsc [...]"
69 case getOpt Permute options args of
71 | any isHelp flags -> putStrLn (usageInfo header options)
72 | any isVersion flags -> putStrLn version
74 isHelp Help = True; isHelp _ = False
75 isVersion Version = True; isVersion _ = False
76 (_, [], []) -> putStrLn (prog++": No input files")
77 (flags, files, []) -> mapM_ (processFile flags) files
80 putStrLn (usageInfo header options)
83 processFile :: [Flag] -> String -> IO ()
84 processFile flags name = do
87 Parser p -> case p (SourcePos name 1) s of
88 Success _ _ _ toks -> output flags name toks
89 Failure (SourcePos name' line) msg -> do
90 putStrLn (name'++":"++show line++": "++msg)
93 ------------------------------------------------------------------------
94 -- A deterministic parser which remembers the text which has been parsed.
96 newtype Parser a = Parser (SourcePos -> String -> ParseResult a)
98 data ParseResult a = Success !SourcePos String String a
99 | Failure !SourcePos String
101 data SourcePos = SourcePos String !Int
103 updatePos :: SourcePos -> Char -> SourcePos
104 updatePos pos@(SourcePos name line) ch = case ch of
105 '\n' -> SourcePos name (line + 1)
108 instance Monad Parser where
109 return a = Parser $ \pos s -> Success pos [] s a
111 Parser $ \pos s -> case m pos s of
112 Success pos' out1 s' a -> case k a of
113 Parser k' -> case k' pos' s' of
114 Success pos'' out2 imp'' b ->
115 Success pos'' (out1++out2) imp'' b
116 Failure pos'' msg -> Failure pos'' msg
117 Failure pos' msg -> Failure pos' msg
118 fail msg = Parser $ \pos _ -> Failure pos msg
120 instance MonadPlus Parser where
122 Parser m `mplus` Parser n =
123 Parser $ \pos s -> case m pos s of
124 success@(Success _ _ _ _) -> success
125 Failure _ _ -> n pos s
127 getPos :: Parser SourcePos
128 getPos = Parser $ \pos s -> Success pos [] s pos
130 setPos :: SourcePos -> Parser ()
131 setPos pos = Parser $ \_ s -> Success pos [] s ()
133 message :: Parser a -> String -> Parser a
134 Parser m `message` msg =
135 Parser $ \pos s -> case m pos s of
136 success@(Success _ _ _ _) -> success
137 Failure pos' _ -> Failure pos' msg
139 catchOutput_ :: Parser a -> Parser String
140 catchOutput_ (Parser m) =
141 Parser $ \pos s -> case m pos s of
142 Success pos' out s' _ -> Success pos' [] s' out
143 Failure pos' msg -> Failure pos' msg
145 fakeOutput :: Parser a -> String -> Parser a
146 Parser m `fakeOutput` out =
147 Parser $ \pos s -> case m pos s of
148 Success pos' _ s' a -> Success pos' out s' a
149 Failure pos' msg -> Failure pos' msg
151 lookAhead :: Parser String
152 lookAhead = Parser $ \pos s -> Success pos [] s s
154 satisfy :: (Char -> Bool) -> Parser Char
156 Parser $ \pos s -> case s of
157 c:cs | p c -> Success (updatePos pos c) [c] cs c
158 _ -> Failure pos "Bad character"
160 char_ :: Char -> Parser ()
162 satisfy (== c) `message` (show c++" expected")
165 anyChar_ :: Parser ()
167 satisfy (const True) `message` "Unexpected end of file"
170 any2Chars_ :: Parser ()
171 any2Chars_ = anyChar_ >> anyChar_
173 many :: Parser a -> Parser [a]
174 many p = many1 p `mplus` return []
176 many1 :: Parser a -> Parser [a]
177 many1 p = liftM2 (:) p (many p)
179 many_ :: Parser a -> Parser ()
180 many_ p = many1_ p `mplus` return ()
182 many1_ :: Parser a -> Parser ()
183 many1_ p = p >> many_ p
185 manySatisfy, manySatisfy1 :: (Char -> Bool) -> Parser String
186 manySatisfy = many . satisfy
187 manySatisfy1 = many1 . satisfy
189 manySatisfy_, manySatisfy1_ :: (Char -> Bool) -> Parser ()
190 manySatisfy_ = many_ . satisfy
191 manySatisfy1_ = many1_ . satisfy
193 ------------------------------------------------------------------------
194 -- Parser of hsc syntax.
197 = Text SourcePos String
198 | Special SourcePos String String
200 parser :: Parser [Token]
203 t <- catchOutput_ text
207 _:_ -> liftM2 (:) (special `fakeOutput` []) parser
208 return (if null t then rest else Text pos t : rest)
215 c:_ | isAlpha c || c == '_' -> do
217 manySatisfy_ (\c' -> isAlphaNum c' || c' == '_' || c' == '\'')
219 c:_ | isHsSymbol c -> do
220 symb <- catchOutput_ (manySatisfy_ isHsSymbol)
223 '-':'-':symb' | all (== '-') symb' -> do
224 return () `fakeOutput` symb
225 manySatisfy_ (/= '\n')
228 return () `fakeOutput` unescapeHashes symb
230 '\"':_ -> do anyChar_; hsString '\"'; text
231 '\'':_ -> do anyChar_; hsString '\''; text
232 '{':'-':_ -> do any2Chars_; linePragma `mplus` hsComment; text
233 _:_ -> do anyChar_; text
235 hsString :: Char -> Parser ()
240 c:_ | c == quote -> anyChar_
245 char_ '\\' `mplus` return ()
247 | otherwise -> do any2Chars_; hsString quote
248 _:_ -> do anyChar_; hsString quote
250 hsComment :: Parser ()
255 '-':'}':_ -> any2Chars_
256 '{':'-':_ -> do any2Chars_; hsComment; hsComment
257 _:_ -> do anyChar_; hsComment
259 linePragma :: Parser ()
263 satisfy (\c -> c == 'L' || c == 'l')
264 satisfy (\c -> c == 'I' || c == 'i')
265 satisfy (\c -> c == 'N' || c == 'n')
266 satisfy (\c -> c == 'E' || c == 'e')
267 manySatisfy1_ isSpace
268 line <- liftM read $ manySatisfy1 isDigit
269 manySatisfy1_ isSpace
271 name <- manySatisfy (/= '\"')
277 setPos (SourcePos name (line - 1))
279 isHsSymbol :: Char -> Bool
280 isHsSymbol '!' = True; isHsSymbol '#' = True; isHsSymbol '$' = True
281 isHsSymbol '%' = True; isHsSymbol '&' = True; isHsSymbol '*' = True
282 isHsSymbol '+' = True; isHsSymbol '.' = True; isHsSymbol '/' = True
283 isHsSymbol '<' = True; isHsSymbol '=' = True; isHsSymbol '>' = True
284 isHsSymbol '?' = True; isHsSymbol '@' = True; isHsSymbol '\\' = True
285 isHsSymbol '^' = True; isHsSymbol '|' = True; isHsSymbol '-' = True
286 isHsSymbol '~' = True
289 unescapeHashes :: String -> String
290 unescapeHashes [] = []
291 unescapeHashes ('#':'#':s) = '#' : unescapeHashes s
292 unescapeHashes (c:s) = c : unescapeHashes s
294 lookAheadC :: Parser String
295 lookAheadC = liftM joinLines lookAhead
298 joinLines ('\\':'\n':s) = joinLines s
299 joinLines (c:s) = c : joinLines s
301 satisfyC :: (Char -> Bool) -> Parser Char
305 '\\':'\n':_ -> do any2Chars_ `fakeOutput` []; satisfyC p
308 charC_ :: Char -> Parser ()
310 satisfyC (== c) `message` (show c++" expected")
313 anyCharC_ :: Parser ()
315 satisfyC (const True) `message` "Unexpected end of file"
318 any2CharsC_ :: Parser ()
319 any2CharsC_ = anyCharC_ >> anyCharC_
321 manySatisfyC :: (Char -> Bool) -> Parser String
322 manySatisfyC = many . satisfyC
324 manySatisfyC_ :: (Char -> Bool) -> Parser ()
325 manySatisfyC_ = many_ . satisfyC
327 special :: Parser Token
329 manySatisfyC_ (\c -> isSpace c && c /= '\n')
334 manySatisfyC_ isSpace
335 sp <- keyArg (== '\n')
338 _ -> keyArg (const False)
340 keyArg :: (Char -> Bool) -> Parser Token
343 key <- keyword `message` "hsc keyword or '{' expected"
344 manySatisfyC_ (\c' -> isSpace c' && c' /= '\n' || eol c')
345 arg <- catchOutput_ (argument eol)
346 return (Special pos key arg)
348 keyword :: Parser String
350 c <- satisfyC (\c' -> isAlpha c' || c' == '_')
351 cs <- manySatisfyC (\c' -> isAlphaNum c' || c' == '_')
354 argument :: (Char -> Bool) -> Parser ()
359 c:_ | eol c -> do anyCharC_; argument eol
361 '\"':_ -> do anyCharC_; cString '\"'; argument eol
362 '\'':_ -> do anyCharC_; cString '\''; argument eol
363 '(':_ -> do anyCharC_; nested ')'; argument eol
365 '/':'*':_ -> do any2CharsC_; cComment; argument eol
367 any2CharsC_; manySatisfyC_ (/= '\n'); argument eol
368 '[':_ -> do anyCharC_; nested ']'; argument eol
370 '{':_ -> do anyCharC_; nested '}'; argument eol
372 _:_ -> do anyCharC_; argument eol
374 nested :: Char -> Parser ()
375 nested c = do argument (== '\n'); charC_ c
377 cComment :: Parser ()
382 '*':'/':_ -> do any2CharsC_
383 _:_ -> do anyCharC_; cComment
385 cString :: Char -> Parser ()
390 c:_ | c == quote -> anyCharC_
391 '\\':_:_ -> do any2CharsC_; cString quote
392 _:_ -> do anyCharC_; cString quote
394 ------------------------------------------------------------------------
395 -- Write the output files.
397 splitName :: String -> (String, String)
399 case break (== '/') name of
400 (file, []) -> ([], file)
401 (dir, sep:rest) -> (dir++sep:restDir, restFile)
403 (restDir, restFile) = splitName rest
405 splitExt :: String -> (String, String)
407 case break (== '.') name of
408 (base, []) -> (base, [])
409 (base, sepRest@(sep:rest))
410 | null restExt -> (base, sepRest)
411 | otherwise -> (base++sep:restBase, restExt)
413 (restBase, restExt) = splitExt rest
415 output :: [Flag] -> String -> [Token] -> IO ()
416 output flags name toks = do
418 let (dir, file) = splitName name
419 (base, ext) = splitExt file
421 (outName, outDir, outBase) <- case [f | Output f <- flags] of
424 last ext == 'c' -> return (dir++base++init ext, dir, base)
425 | ext == ".hs" -> return (dir++base++"_out.hs", dir, base)
426 | otherwise -> return (dir++base++".hs", dir, base)
428 (dir', file') = splitName f
429 (base', _) = splitExt file'
430 in return (f, dir', base')
431 _ -> onlyOne "output file"
433 supportDirBase <- case [f | Support f <- flags] of
434 [] -> return (outDir++"Hs"++outBase)
436 _ -> onlyOne "support file"
438 let cProgName = outDir++outBase++"_make.c"
439 oProgName = outDir++outBase++"_make.o"
440 progName = outDir++outBase++"_make"
441 outHName = supportDirBase++".h"
442 outCName = supportDirBase++".c"
445 | null dir = "./"++progName
446 | otherwise = progName
448 let specials = [(pos, key, arg) | Special pos key arg <- toks]
450 let needsC = any (\(_, key, _) -> key == "def") specials
453 let includeGuard = map fixChar outHName
455 fixChar c | isAlphaNum c = toUpper c
458 compiler <- case [c | Compiler c <- flags] of
461 _ -> onlyOne "compiler"
463 linker <- case [l | Linker l <- flags] of
464 [] -> return defaultCompiler
466 _ -> onlyOne "linker"
468 writeFile cProgName $
469 concatMap outFlagHeaderCProg flags++
470 concatMap outHeaderCProg specials++
471 "\nint main (void)\n{\n"++
472 outHeaderHs flags (if needsH then Just outHName else Nothing) specials++
473 outHsLine (SourcePos name 0)++
474 concatMap outTokenHs toks++
477 unless (null [() | NoCompile <- flags]) $ exitWith ExitSuccess
479 compilerStatus <- system $
482 concat [" "++f | CompFlag f <- flags]++
485 case compilerStatus of
486 e@(ExitFailure _) -> exitWith e
490 linkerStatus <- system $
492 concat [" "++f | LinkFlag f <- flags]++
496 e@(ExitFailure _) -> exitWith e
500 system (execProgName++" >"++outName)
503 when needsH $ writeFile outHName $
504 "#ifndef "++includeGuard++"\n\
505 \#define "++includeGuard++"\n\
506 \#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
509 \#include <HsFFI.h>\n\
512 \#define HsChar int\n\
514 concatMap outFlagH flags++
515 concatMap outTokenH specials++
518 when needsC $ writeFile outCName $
519 "#include \""++outHName++"\"\n"++
520 concatMap outTokenC specials
522 onlyOne :: String -> IO a
524 putStrLn ("Only one "++what++" may be specified")
527 outFlagHeaderCProg :: Flag -> String
528 outFlagHeaderCProg (Template t) = "#include \""++t++"\"\n"
529 outFlagHeaderCProg (Include f) = "#include "++f++"\n"
530 outFlagHeaderCProg (Define n Nothing) = "#define "++n++"\n"
531 outFlagHeaderCProg (Define n (Just v)) = "#define "++n++" "++v++"\n"
532 outFlagHeaderCProg _ = ""
534 outHeaderCProg :: (SourcePos, String, String) -> String
535 outHeaderCProg (pos, key, arg) = case key of
536 "include" -> outCLine pos++"#include "++arg++"\n"
537 "define" -> outCLine pos++"#define "++arg++"\n"
538 "undef" -> outCLine pos++"#undef "++arg++"\n"
540 's':'t':'r':'u':'c':'t':' ':_ -> outCLine pos++arg++"\n"
541 't':'y':'p':'e':'d':'e':'f':' ':_ -> outCLine pos++arg++"\n"
543 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
544 "let" -> case break (== '=') arg of
546 (header, _:body) -> case break isSpace header of
549 "#define hsc_"++name++"("++dropWhile isSpace args++") \
550 \printf ("++joinLines body++");\n"
553 joinLines = concat . intersperse " \\\n" . lines
555 outHeaderHs :: [Flag] -> Maybe String -> [(SourcePos, String, String)] -> String
556 outHeaderHs flags inH toks =
557 "#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
558 \ printf (\"{-# OPTIONS -optc-D__GLASGOW_HASKELL__=%d #-}\\n\", \
559 \__GLASGOW_HASKELL__);\n\
562 Nothing -> concatMap outFlag flags++concatMap outSpecial toks
563 Just f -> outOption ("-#include \""++f++"\"")
565 outFlag (Include f) = outOption ("-#include "++f)
566 outFlag (Define n Nothing) = outOption ("-optc-D"++n)
567 outFlag (Define n (Just v)) = outOption ("-optc-D"++n++"="++v)
569 outSpecial (pos, key, arg) = case key of
570 "include" -> outOption ("-#include "++arg)
571 "define" | goodForOptD arg -> outOption ("-optc-D"++toOptD arg)
573 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
575 goodForOptD arg = case arg of
577 c:_ | isSpace c -> True
580 toOptD arg = case break isSpace arg of
582 (name, _:value) -> name++'=':dropWhile isSpace value
583 outOption s = " printf (\"{-# OPTIONS %s #-}\\n\", \""++
584 showCString s++"\");\n"
586 outTokenHs :: Token -> String
587 outTokenHs (Text pos text) =
588 case break (== '\n') text of
589 (all, []) -> outText all
591 outText (first++"\n")++
595 outText s = " fputs (\""++showCString s++"\", stdout);\n"
596 outTokenHs (Special pos key arg) =
602 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
604 "enum" -> outCLine pos++outEnum arg
605 _ -> outCLine pos++" hsc_"++key++" ("++arg++");\n"
607 outEnum :: String -> String
609 case break (== ',') arg of
611 (t, _:afterT) -> case break (== ',') afterT of
614 enums (_:s) = case break (== ',') s of
616 this = case break (== '=') $ dropWhile isSpace enum of
618 " hsc_enum ("++t++", "++f++", \
619 \hsc_haskellize (\""++name++"\"), "++
622 " hsc_enum ("++t++", "++f++", \
623 \printf (\"%s\", \""++hsName++"\"), "++
628 outFlagH :: Flag -> String
629 outFlagH (Include f) = "#include "++f++"\n"
630 outFlagH (Define n Nothing) = "#define "++n++"\n"
631 outFlagH (Define n (Just v)) = "#define "++n++" "++v++"\n"
634 outTokenH :: (SourcePos, String, String) -> String
635 outTokenH (pos, key, arg) =
637 "include" -> outCLine pos++"#include "++arg++"\n"
638 "define" -> outCLine pos++"#define " ++arg++"\n"
639 "undef" -> outCLine pos++"#undef " ++arg++"\n"
640 "def" -> outCLine pos++case arg of
641 's':'t':'r':'u':'c':'t':' ':_ -> arg++"\n"
642 't':'y':'p':'e':'d':'e':'f':' ':_ -> arg++"\n"
643 'i':'n':'l':'i':'n':'e':' ':_ ->
648 _ -> "extern "++header++";\n"
649 where header = takeWhile (\c -> c /= '{' && c /= '=') arg
650 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
653 outTokenC :: (SourcePos, String, String) -> String
654 outTokenC (pos, key, arg) =
657 's':'t':'r':'u':'c':'t':' ':_ -> ""
658 't':'y':'p':'e':'d':'e':'f':' ':_ -> ""
659 'i':'n':'l':'i':'n':'e':' ':_ ->
665 "\n#ifndef __GNUC__\n\
670 _ -> outCLine pos++arg++"\n"
671 where (header, body) = span (\c -> c /= '{' && c /= '=') arg
672 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
675 conditional :: String -> Bool
676 conditional "if" = True
677 conditional "ifdef" = True
678 conditional "ifndef" = True
679 conditional "elif" = True
680 conditional "else" = True
681 conditional "endif" = True
682 conditional "error" = True
683 conditional "warning" = True
684 conditional _ = False
686 outCLine :: SourcePos -> String
687 outCLine (SourcePos name line) =
688 "# "++show line++" \""++showCString (snd (splitName name))++"\"\n"
690 outHsLine :: SourcePos -> String
691 outHsLine (SourcePos name line) =
692 " hsc_line ("++show (line + 1)++", \""++
693 showCString (snd (splitName name))++"\");\n"
695 showCString :: String -> String
696 showCString = concatMap showCChar
698 showCChar '\"' = "\\\""
699 showCChar '\'' = "\\\'"
700 showCChar '?' = "\\?"
701 showCChar '\\' = "\\\\"
702 showCChar c | c >= ' ' && c <= '~' = [c]
703 showCChar '\a' = "\\a"
704 showCChar '\b' = "\\b"
705 showCChar '\f' = "\\f"
706 showCChar '\n' = "\\n\"\n \""
707 showCChar '\r' = "\\r"
708 showCChar '\t' = "\\t"
709 showCChar '\v' = "\\v"
711 intToDigit (ord c `quot` 64),
712 intToDigit (ord c `quot` 8 `mod` 8),
713 intToDigit (ord c `mod` 8)]