1 ------------------------------------------------------------------------
2 -- $Id: Main.hs,v 1.26 2001/03/16 09:07:41 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"
34 include :: String -> Flag
35 include s@('\"':_) = Include s
36 include s@('<' :_) = Include s
37 include s = Include ("\""++s++"\"")
39 options :: [OptDescr Flag]
41 Option "t" ["template"] (ReqArg Template "FILE") "template file",
42 Option "c" ["cc"] (ReqArg Compiler "PROG") "C compiler to use",
43 Option "l" ["ld"] (ReqArg Linker "PROG") "linker to use",
44 Option "C" ["cflag"] (ReqArg CompFlag "FLAG") "flag to pass to the C compiler",
45 Option "I" [] (ReqArg (CompFlag . ("-I"++))
46 "DIR") "passed to the C compiler",
47 Option "L" ["lflag"] (ReqArg LinkFlag "FLAG") "flag to pass to the linker",
48 Option "" ["no-compile"] (NoArg NoCompile) "stop after writing *.hs_make.c",
49 Option "i" ["include"] (ReqArg include "FILE") "as if placed in the source",
50 Option "" ["help"] (NoArg Help) "display this help and exit",
51 Option "" ["version"] (NoArg Version) "output version information and exit"]
56 let header = "Usage: "++prog++" [OPTIONS] INPUT.hsc [...]"
58 case getOpt Permute options args of
60 | any isHelp flags -> putStrLn (usageInfo header options)
61 | any isVersion flags -> putStrLn version
63 isHelp Help = True; isHelp _ = False
64 isVersion Version = True; isVersion _ = False
65 (_, [], []) -> putStrLn (prog++": No input files")
66 (flags, files, []) -> mapM_ (processFile flags) files
69 putStrLn (usageInfo header options)
72 processFile :: [Flag] -> String -> IO ()
73 processFile flags name = do
76 Parser p -> case p (SourcePos name 1) s of
77 Success _ _ _ toks -> output flags name toks
78 Failure (SourcePos name' line) msg -> do
79 putStrLn (name'++":"++show line++": "++msg)
82 ------------------------------------------------------------------------
83 -- A deterministic parser which remembers the text which has been parsed.
85 newtype Parser a = Parser (SourcePos -> String -> ParseResult a)
87 data ParseResult a = Success !SourcePos String String a
88 | Failure !SourcePos String
90 data SourcePos = SourcePos String !Int
92 updatePos :: SourcePos -> Char -> SourcePos
93 updatePos pos@(SourcePos name line) ch = case ch of
94 '\n' -> SourcePos name (line + 1)
97 instance Monad Parser where
98 return a = Parser $ \pos s -> Success pos [] s a
100 Parser $ \pos s -> case m pos s of
101 Success pos' out1 s' a -> case k a of
102 Parser k' -> case k' pos' s' of
103 Success pos'' out2 imp'' b ->
104 Success pos'' (out1++out2) imp'' b
105 Failure pos'' msg -> Failure pos'' msg
106 Failure pos' msg -> Failure pos' msg
107 fail msg = Parser $ \pos _ -> Failure pos msg
109 instance MonadPlus Parser where
111 Parser m `mplus` Parser n =
112 Parser $ \pos s -> case m pos s of
113 success@(Success _ _ _ _) -> success
114 Failure _ _ -> n pos s
116 getPos :: Parser SourcePos
117 getPos = Parser $ \pos s -> Success pos [] s pos
119 setPos :: SourcePos -> Parser ()
120 setPos pos = Parser $ \_ s -> Success pos [] s ()
122 message :: Parser a -> String -> Parser a
123 Parser m `message` msg =
124 Parser $ \pos s -> case m pos s of
125 success@(Success _ _ _ _) -> success
126 Failure pos' _ -> Failure pos' msg
128 catchOutput_ :: Parser a -> Parser String
129 catchOutput_ (Parser m) =
130 Parser $ \pos s -> case m pos s of
131 Success pos' out s' _ -> Success pos' [] s' out
132 Failure pos' msg -> Failure pos' msg
134 fakeOutput :: Parser a -> String -> Parser a
135 Parser m `fakeOutput` out =
136 Parser $ \pos s -> case m pos s of
137 Success pos' _ s' a -> Success pos' out s' a
138 Failure pos' msg -> Failure pos' msg
140 lookAhead :: Parser String
141 lookAhead = Parser $ \pos s -> Success pos [] s s
143 satisfy :: (Char -> Bool) -> Parser Char
145 Parser $ \pos s -> case s of
146 c:cs | p c -> Success (updatePos pos c) [c] cs c
147 _ -> Failure pos "Bad character"
149 char_ :: Char -> Parser ()
151 satisfy (== c) `message` (show c++" expected")
154 anyChar_ :: Parser ()
156 satisfy (const True) `message` "Unexpected end of file"
159 any2Chars_ :: Parser ()
160 any2Chars_ = anyChar_ >> anyChar_
162 many :: Parser a -> Parser [a]
163 many p = many1 p `mplus` return []
165 many1 :: Parser a -> Parser [a]
166 many1 p = liftM2 (:) p (many p)
168 many_ :: Parser a -> Parser ()
169 many_ p = many1_ p `mplus` return ()
171 many1_ :: Parser a -> Parser ()
172 many1_ p = p >> many_ p
174 manySatisfy, manySatisfy1 :: (Char -> Bool) -> Parser String
175 manySatisfy = many . satisfy
176 manySatisfy1 = many1 . satisfy
178 manySatisfy_, manySatisfy1_ :: (Char -> Bool) -> Parser ()
179 manySatisfy_ = many_ . satisfy
180 manySatisfy1_ = many1_ . satisfy
182 ------------------------------------------------------------------------
183 -- Parser of hsc syntax.
186 = Text SourcePos String
187 | Special SourcePos String String
189 parser :: Parser [Token]
192 t <- catchOutput_ text
196 _:_ -> liftM2 (:) (special `fakeOutput` []) parser
197 return (if null t then rest else Text pos t : rest)
204 c:_ | isAlpha c || c == '_' -> do
206 manySatisfy_ (\c' -> isAlphaNum c' || c' == '_' || c' == '\'')
208 c:_ | isHsSymbol c -> do
209 symb <- catchOutput_ (manySatisfy_ isHsSymbol)
212 '-':'-':symb' | all (== '-') symb' -> do
213 return () `fakeOutput` symb
214 manySatisfy_ (/= '\n')
217 return () `fakeOutput` unescapeHashes symb
219 '\"':_ -> do anyChar_; hsString '\"'; text
220 '\'':_ -> do anyChar_; hsString '\''; text
221 '{':'-':_ -> do any2Chars_; linePragma `mplus` hsComment; text
222 _:_ -> do anyChar_; text
224 hsString :: Char -> Parser ()
229 c:_ | c == quote -> anyChar_
234 char_ '\\' `mplus` return ()
236 | otherwise -> do any2Chars_; hsString quote
237 _:_ -> do anyChar_; hsString quote
239 hsComment :: Parser ()
244 '-':'}':_ -> any2Chars_
245 '{':'-':_ -> do any2Chars_; hsComment; hsComment
246 _:_ -> do anyChar_; hsComment
248 linePragma :: Parser ()
252 satisfy (\c -> c == 'L' || c == 'l')
253 satisfy (\c -> c == 'I' || c == 'i')
254 satisfy (\c -> c == 'N' || c == 'n')
255 satisfy (\c -> c == 'E' || c == 'e')
256 manySatisfy1_ isSpace
257 line <- liftM read $ manySatisfy1 isDigit
258 manySatisfy1_ isSpace
260 name <- manySatisfy (/= '\"')
266 setPos (SourcePos name (line - 1))
268 isHsSymbol :: Char -> Bool
269 isHsSymbol '!' = True; isHsSymbol '#' = True; isHsSymbol '$' = True
270 isHsSymbol '%' = True; isHsSymbol '&' = True; isHsSymbol '*' = True
271 isHsSymbol '+' = True; isHsSymbol '.' = True; isHsSymbol '/' = True
272 isHsSymbol '<' = True; isHsSymbol '=' = True; isHsSymbol '>' = True
273 isHsSymbol '?' = True; isHsSymbol '@' = True; isHsSymbol '\\' = True
274 isHsSymbol '^' = True; isHsSymbol '|' = True; isHsSymbol '-' = True
275 isHsSymbol '~' = True
278 unescapeHashes :: String -> String
279 unescapeHashes [] = []
280 unescapeHashes ('#':'#':s) = '#' : unescapeHashes s
281 unescapeHashes (c:s) = c : unescapeHashes s
283 lookAheadC :: Parser String
284 lookAheadC = liftM joinLines lookAhead
287 joinLines ('\\':'\n':s) = joinLines s
288 joinLines (c:s) = c : joinLines s
290 satisfyC :: (Char -> Bool) -> Parser Char
294 '\\':'\n':_ -> do any2Chars_ `fakeOutput` []; satisfyC p
297 charC_ :: Char -> Parser ()
299 satisfyC (== c) `message` (show c++" expected")
302 anyCharC_ :: Parser ()
304 satisfyC (const True) `message` "Unexpected end of file"
307 any2CharsC_ :: Parser ()
308 any2CharsC_ = anyCharC_ >> anyCharC_
310 manySatisfyC :: (Char -> Bool) -> Parser String
311 manySatisfyC = many . satisfyC
313 manySatisfyC_ :: (Char -> Bool) -> Parser ()
314 manySatisfyC_ = many_ . satisfyC
316 special :: Parser Token
318 manySatisfyC_ (\c -> isSpace c && c /= '\n')
323 manySatisfyC_ isSpace
324 sp <- keyArg (== '\n')
327 _ -> keyArg (const False)
329 keyArg :: (Char -> Bool) -> Parser Token
332 key <- keyword `message` "hsc keyword or '{' expected"
333 manySatisfyC_ (\c' -> isSpace c' && c' /= '\n' || eol c')
334 arg <- catchOutput_ (argument eol)
335 return (Special pos key arg)
337 keyword :: Parser String
339 c <- satisfyC (\c' -> isAlpha c' || c' == '_')
340 cs <- manySatisfyC (\c' -> isAlphaNum c' || c' == '_')
343 argument :: (Char -> Bool) -> Parser ()
348 c:_ | eol c -> do anyCharC_; argument eol
350 '\"':_ -> do anyCharC_; cString '\"'; argument eol
351 '\'':_ -> do anyCharC_; cString '\''; argument eol
352 '(':_ -> do anyCharC_; nested ')'; argument eol
354 '/':'*':_ -> do any2CharsC_; cComment; argument eol
356 any2CharsC_; manySatisfyC_ (/= '\n'); argument eol
357 '[':_ -> do anyCharC_; nested ']'; argument eol
359 '{':_ -> do anyCharC_; nested '}'; argument eol
361 _:_ -> do anyCharC_; argument eol
363 nested :: Char -> Parser ()
364 nested c = do argument (== '\n'); charC_ c
366 cComment :: Parser ()
371 '*':'/':_ -> do any2CharsC_
372 _:_ -> do anyCharC_; cComment
374 cString :: Char -> Parser ()
379 c:_ | c == quote -> anyCharC_
380 '\\':_:_ -> do any2CharsC_; cString quote
381 _:_ -> do anyCharC_; cString quote
383 ------------------------------------------------------------------------
384 -- Output the output files.
386 output :: [Flag] -> String -> [Token] -> IO ()
387 output flags name toks = let
388 baseName = case reverse name of
389 'c':base -> reverse base
391 cProgName = baseName++"_make.c"
392 oProgName = baseName++"_make.o"
393 progName = baseName++"_make"
395 outHName = baseName++".h"
396 outCName = baseName++".c"
398 execProgName = case progName of
402 specials = [(pos, key, arg) | Special pos key arg <- toks]
404 needsC = any (\(_, key, _) -> key == "def") specials
407 includeGuard = map fixChar outHName
409 fixChar c | isAlphaNum c = toUpper c
414 compiler <- case [c | Compiler c <- flags] of
417 _ -> onlyOne "compiler"
418 linker <- case [l | Linker l <- flags] of
419 [] -> return defaultCompiler
421 _ -> onlyOne "linker"
423 writeFile cProgName $
424 concat ["#include \""++t++"\"\n" | Template t <- flags]++
425 concat ["#include "++f++"\n" | Include f <- flags]++
426 outHeaderCProg specials++
427 "\nint main (void)\n{\n"++
428 outHeaderHs flags (if needsH then Just outHName else Nothing) specials++
429 outHsLine (SourcePos name 0)++
430 concatMap outTokenHs toks++
433 unless (null [() | NoCompile <- flags]) $ exitWith ExitSuccess
435 compilerStatus <- system $
438 concat [" "++f | CompFlag f <- flags]++
441 case compilerStatus of
442 e@(ExitFailure _) -> exitWith e
446 linkerStatus <- system $
448 concat [" "++f | LinkFlag f <- flags]++
452 e@(ExitFailure _) -> exitWith e
456 system (execProgName++" >"++outHsName)
459 when needsH $ writeFile outHName $
460 "#ifndef "++includeGuard++"\n\
461 \#define "++includeGuard++"\n\
462 \#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
465 \#include <HsFFI.h>\n\
468 \#define HsChar int\n\
470 concat ["#include "++n++"\n" | Include n <- flags]++
471 concatMap outTokenH specials++
474 when needsC $ writeFile outCName $
475 "#include \""++outHName++"\"\n"++
476 concatMap outTokenC specials
478 onlyOne :: String -> IO a
480 putStrLn ("Only one "++what++" may be specified")
483 outHeaderCProg :: [(SourcePos, String, String)] -> String
485 concatMap $ \(pos, key, arg) -> case key of
486 "include" -> outCLine pos++"#include "++arg++"\n"
487 "define" -> outCLine pos++"#define "++arg++"\n"
488 "undef" -> outCLine pos++"#undef "++arg++"\n"
490 's':'t':'r':'u':'c':'t':' ':_ -> outCLine pos++arg++"\n"
491 't':'y':'p':'e':'d':'e':'f':' ':_ -> outCLine pos++arg++"\n"
493 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
494 "let" -> case break (== '=') arg of
496 (header, _:body) -> case break isSpace header of
499 "#define hsc_"++name++"("++dropWhile isSpace args++") \
500 \printf ("++joinLines body++");\n"
503 joinLines = concat . intersperse " \\\n" . lines
505 outHeaderHs :: [Flag] -> Maybe String -> [(SourcePos, String, String)] -> String
506 outHeaderHs flags inH toks =
507 "#if __GLASGOW_HASKELL__ && __GLASGOW_HASKELL__ < 409\n\
508 \ printf (\"{-# OPTIONS -optc-D__GLASGOW_HASKELL__=%d #-}\\n\", \
509 \__GLASGOW_HASKELL__);\n\
512 concatMap outSpecial toks
514 outSpecial (pos, key, arg) = case key of
515 "include" -> case inH of
516 Nothing -> outOption ("-#include "++arg)
518 "define" -> case inH of
519 Nothing | goodForOptD arg -> outOption ("-optc-D"++toOptD arg)
521 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
523 goodForOptD arg = case arg of
525 c:_ | isSpace c -> True
528 toOptD arg = case break isSpace arg of
530 (name, _:value) -> name++'=':dropWhile isSpace value
532 outOption ("-#include "++name++"")
533 | name <- case inH of
534 Nothing -> [name | Include name <- flags]
535 Just name -> ["\""++name++"\""]]
536 outOption s = " printf (\"{-# OPTIONS %s #-}\\n\", \""++
537 showCString s++"\");\n"
539 outTokenHs :: Token -> String
540 outTokenHs (Text pos text) =
541 case break (== '\n') text of
542 (all, []) -> outText all
544 outText (first++"\n")++
548 outText s = " fputs (\""++showCString s++"\", stdout);\n"
549 outTokenHs (Special pos key arg) =
555 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
557 "enum" -> outCLine pos++outEnum arg
558 _ -> outCLine pos++" hsc_"++key++" ("++arg++");\n"
560 outEnum :: String -> String
562 case break (== ',') arg of
564 (t, _:afterT) -> case break (== ',') afterT of
567 enums (_:s) = case break (== ',') s of
569 this = case break (== '=') $ dropWhile isSpace enum of
571 " hsc_enum ("++t++", "++f++", \
572 \hsc_haskellize (\""++name++"\"), "++
575 " hsc_enum ("++t++", "++f++", \
576 \printf (\"%s\", \""++hsName++"\"), "++
581 outTokenH :: (SourcePos, String, String) -> String
582 outTokenH (pos, key, arg) =
584 "include" -> outCLine pos++"#include "++arg++"\n"
585 "define" -> outCLine pos++"#define " ++arg++"\n"
586 "undef" -> outCLine pos++"#undef " ++arg++"\n"
587 "def" -> outCLine pos++case arg of
588 's':'t':'r':'u':'c':'t':' ':_ -> arg++"\n"
589 't':'y':'p':'e':'d':'e':'f':' ':_ -> arg++"\n"
590 'i':'n':'l':'i':'n':'e':' ':_ ->
595 _ -> "extern "++header++";\n"
596 where header = takeWhile (\c -> c /= '{' && c /= '=') arg
597 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
600 outTokenC :: (SourcePos, String, String) -> String
601 outTokenC (pos, key, arg) =
604 's':'t':'r':'u':'c':'t':' ':_ -> ""
605 't':'y':'p':'e':'d':'e':'f':' ':_ -> ""
606 'i':'n':'l':'i':'n':'e':' ':_ ->
612 "\n#ifndef __GNUC__\n\
617 _ -> outCLine pos++arg++"\n"
618 where (header, body) = span (\c -> c /= '{' && c /= '=') arg
619 _ | conditional key -> outCLine pos++"#"++key++" "++arg++"\n"
622 conditional :: String -> Bool
623 conditional "if" = True
624 conditional "ifdef" = True
625 conditional "ifndef" = True
626 conditional "elif" = True
627 conditional "else" = True
628 conditional "endif" = True
629 conditional "error" = True
630 conditional "warning" = True
631 conditional _ = False
633 outCLine :: SourcePos -> String
634 outCLine (SourcePos name line) =
635 "# "++show line++" \""++showCString (basename name)++"\"\n"
637 outHsLine :: SourcePos -> String
638 outHsLine (SourcePos name line) =
639 " hsc_line ("++show (line + 1)++", \""++
640 showCString (basename name)++"\");\n"
642 basename :: String -> String
643 basename s = case break (== '/') s of
645 (_, _:rest) -> basename rest
647 showCString :: String -> String
648 showCString = concatMap showCChar
650 showCChar '\"' = "\\\""
651 showCChar '\'' = "\\\'"
652 showCChar '?' = "\\?"
653 showCChar '\\' = "\\\\"
654 showCChar c | c >= ' ' && c <= '~' = [c]
655 showCChar '\a' = "\\a"
656 showCChar '\b' = "\\b"
657 showCChar '\f' = "\\f"
658 showCChar '\n' = "\\n\"\n \""
659 showCChar '\r' = "\\r"
660 showCChar '\t' = "\\t"
661 showCChar '\v' = "\\v"
663 intToDigit (ord c `quot` 64),
664 intToDigit (ord c `quot` 8 `mod` 8),
665 intToDigit (ord c `mod` 8)]