Sockeye: clean up
[barrelfish] / tools / sockeye / Main.hs
1 {-
2   SockeyeMain.hs: Sockeye
3
4   Copyright (c) 2017, ETH Zurich.
5
6   All rights reserved.
7
8   This file is distributed under the terms in the attached LICENSE file.
9   If you do not find this file, copies can be found by writing to:
10   ETH Zurich D-INFK, CAB F.78, Universitaetstr. 6, CH-8092 Zurich,
11   Attn: Systems Group.
12 -}
13
14 module Main where
15
16 import Control.Monad
17
18 import qualified Data.Map as Map
19
20 import System.Console.GetOpt
21 import System.Exit
22 import System.Environment
23 import System.IO
24
25 import qualified SockeyeASTParser as ParseAST
26 import qualified SockeyeAST as AST
27 import qualified SockeyeASTDecodingNet as NetAST
28
29 import SockeyeParser
30 import SockeyeChecker
31 import SockeyeNetBuilder
32
33 import qualified SockeyeBackendProlog as Prolog
34
35 {- Exit codes -}
36 usageError :: ExitCode
37 usageError = ExitFailure 1
38
39 parseError :: ExitCode
40 parseError = ExitFailure 2
41
42 checkError :: ExitCode
43 checkError = ExitFailure 3
44
45 buildError :: ExitCode
46 buildError = ExitFailure 4
47
48 {- Compilation targets -}
49 data Target = None | Prolog
50
51 {- Possible options for the Sockeye Compiler -}
52 data Options = Options { optInputFile  :: FilePath
53                        , optTarget     :: Target
54                        , optOutputFile :: Maybe FilePath
55                        }
56
57 {- Default options -}
58 defaultOptions :: Options
59 defaultOptions = Options { optInputFile  = ""
60                          , optTarget     = Prolog
61                          , optOutputFile = Nothing
62                          }
63
64 {- Set the input file name -}
65 optSetInputFileName :: FilePath -> Options -> Options
66 optSetInputFileName f o = o { optInputFile = f }
67
68 {- Set the target -}
69 optSetTarget :: Target -> Options -> Options
70 optSetTarget t o = o { optTarget = t }
71
72 {- Set the outpue file name -}
73 optSetOutputFile :: Maybe String -> Options -> Options
74 optSetOutputFile f o = o { optOutputFile = f }
75
76 {- Prints usage information possibly with usage errors -}
77 usage :: [String] -> IO ()
78 usage errors = do
79     prg <- getProgName
80     let usageString = "Usage: " ++ prg ++ " [options] file\nOptions:"
81     hPutStrLn stderr $ usageInfo (concat errors ++ usageString) options
82     hPutStrLn stderr "The backend (capital letter options) specified last takes precedence."
83
84
85 {- Setup option parser -}
86 options :: [OptDescr (Options -> IO Options)]
87 options = 
88     [ Option "P" ["Prolog"]
89         (NoArg (\opts -> return $ optSetTarget Prolog opts))
90         "Generate a prolog file that can be loaded into the SKB (default)."
91     , Option "C" ["Check"]
92         (NoArg (\opts -> return $ optSetTarget None opts))
93         "Just check the file, do not compile."
94     , Option "o" ["output-file"]
95         (ReqArg (\f opts -> return $ optSetOutputFile (Just f) opts) "FILE")
96         "If no output file is specified the compilation result is written to stdout."
97     , Option "h" ["help"]
98         (NoArg (\_ -> do
99                     usage []
100                     exitWith ExitSuccess))
101         "Show help."
102     ]
103
104 {- evaluates the compiler options -}
105 compilerOpts :: [String] -> IO (Options)
106 compilerOpts argv =
107     case getOpt Permute options argv of
108         (actions, fs, []) -> do
109             opts <- foldl (>>=) (return defaultOptions) actions
110             case fs of
111                 []  -> do
112                     usage ["No input file\n"]
113                     exitWith usageError
114                 [f] -> return $ optSetInputFileName f opts
115                 _   -> do
116                     usage ["Multiple input files not supported\n"]
117                     exitWith usageError
118
119         (_, _, errors) -> do
120             usage errors
121             exitWith $ usageError
122
123 {- Parse Sockeye and resolve imports -}
124 parseSpec :: FilePath -> IO (ParseAST.SockeyeSpec)
125 parseSpec file = do
126     let
127         rootImport = ParseAST.Import file
128     specMap <- parseWithImports Map.empty rootImport
129     let
130         specs = Map.elems specMap
131         topLevelSpec = specMap Map.! file
132         modules = concat $ map ParseAST.modules specs
133     return topLevelSpec
134         { ParseAST.imports = []
135         , ParseAST.modules = modules
136         }
137     where
138         parseWithImports importMap (ParseAST.Import fileName) = do
139             let
140                 file = if '.' `elem` fileName
141                     then fileName
142                     else fileName ++ ".soc"
143             if file `Map.member` importMap
144                 then return importMap
145                 else do
146                     ast <- parseFile file
147                     let
148                         specMap = Map.insert file ast importMap
149                         imports = ParseAST.imports ast
150                     foldM parseWithImports specMap imports
151
152 {- Runs the parser on a single file -}
153 parseFile :: FilePath -> IO (ParseAST.SockeyeSpec)
154 parseFile file = do
155     src <- readFile file
156     case parseSockeye file src of
157         Left err -> do
158             hPutStrLn stderr $ "Parse error at " ++ show err
159             exitWith parseError
160         Right ast -> return ast
161
162 {- Runs the checker -}
163 checkAST :: ParseAST.SockeyeSpec -> IO AST.SockeyeSpec
164 checkAST parsedAst = do
165     case checkSockeye parsedAst of 
166         Left fail -> do
167             hPutStr stderr $ show fail
168             exitWith checkError
169         Right intermAst -> return intermAst
170
171 {- Builds the decoding net from the Sockeye AST -}
172 buildNet :: AST.SockeyeSpec -> IO NetAST.NetSpec
173 buildNet ast = do
174     case sockeyeBuildNet ast of 
175         Left fail -> do
176             hPutStr stderr $ show fail
177             exitWith buildError
178         Right netAst -> return netAst
179
180 {- Compiles the AST with the appropriate backend -}
181 compile :: Target -> NetAST.NetSpec -> IO String
182 compile None     _   = return ""
183 compile Prolog   ast = return $ Prolog.compile ast
184
185 {- Outputs the compilation result -}
186 output :: Maybe FilePath -> String -> IO ()
187 output outFile out = do
188     case outFile of
189         Nothing -> putStr out
190         Just f  -> writeFile f out
191
192 main = do
193     args <- getArgs
194     opts <- compilerOpts args
195     let inFile = optInputFile opts
196     parsedAst <- parseSpec inFile
197     ast <- checkAST parsedAst
198     netAst <- buildNet ast
199     out <- compile (optTarget opts) netAst
200     output (optOutputFile opts) out
201