@@ -1,92 +1,137 @@
1{-# OPTIONS_GHC -W #-}
1{-# OPTIONS_GHC -W #-}
2module Build.File (build) where
2module Build.File (build) where
4−import Control.Monad (when)
4+import Control.Applicative ((<$>))
5−import qualified Data.Binary as Binary
5+import Control.Monad.Error (runErrorT)
6−import qualified Data.Map as Map
6+import Control.Monad.RWS.Strict
7import System.Directory
7import System.Directory
8import System.Exit
8import System.Exit
9import System.FilePath
9import System.FilePath
10import System.IO
10import System.IO
12+import qualified Data.Binary as Binary
13+import qualified Data.Maybe as Maybe
14+import qualified Data.Map as Map
15+import qualified Data.ByteString.Lazy as L
17+import qualified Build.Dependencies as Deps
18+import qualified Build.Flags as Flag
19+import qualified Build.Interface as Interface
20+import qualified Build.Print as Print
21+import qualified Build.Source as Source
22+import qualified Build.Utils as Utils
23+import qualified Generate.JavaScript as JS
24+import qualified Parse.Module as Parser
25+import qualified SourceSyntax.Module as M
12import qualified Transform.Canonicalize as Canonical
26import qualified Transform.Canonicalize as Canonical
14−import qualified Data.ByteString.Lazy as L
28+-- Reader: Runtime flags, always accessible
15−
29+-- Writer: Remember the last module to be accessed
16−import qualified Build.Utils as Utils
30+-- State: Build up a map of the module interfaces
17−import qualified Build.Flags as Flag
31+type BuildT m a = RWST Flag.Flags (Last String) BInterfaces m a
18−import qualified Build.Interface as Interface
32+type Build a = BuildT IO a
19−import qualified Build.Print as Print
33+
20−import qualified Build.Source as Source
34+-- Interfaces, remembering if something was recompiled
21−import qualified Generate.JavaScript as JS
35+type BInterfaces = Map.Map String (Bool, M.ModuleInterface)
22−import qualified Parse.Module as Parser
36+
23−import qualified SourceSyntax.Module as M
25−build :: Flag.Flags -> Int -> M.Interfaces -> String -> [FilePath]
37+evalBuild :: Flag.Flags -> M.Interfaces -> Build () -> IO (Maybe String)
26− -> IO (String, M.Interfaces)
38+evalBuild flags interfaces b = do
27−build _ _ interfaces moduleName [] =
39+ (_, s) <- evalRWST b flags (fmap notUpdated interfaces)
28− return (moduleName, interfaces)
40+ return . getLast $ s
29−build flags numModules interfaces _ (filePath:rest) =
41+ where
30− do (name,interface) <-
42+ notUpdated i = (False, i)
31− build1 flags (numModules - length rest) numModules interfaces filePath
43+
32− let interfaces' = Map.insert name interface interfaces
44+-- | Builds a list of files, returning the moduleName of the last one.
33− build flags numModules interfaces' name rest
45+-- Returns \"\" if the list is empty
34−
46+build :: Flag.Flags -> M.Interfaces -> [FilePath] -> IO String
35−
47+build flags is = fmap (Maybe.fromMaybe "") . evalBuild flags is . buildAll
36−build1 :: Flag.Flags -> Int -> Int -> M.Interfaces -> FilePath
48+
37− -> IO (String, M.ModuleInterface)
49+buildAll :: [FilePath] -> Build ()
38−build1 flags moduleNum numModules interfaces filePath =
50+buildAll fs = mapM_ (uncurry build1) (zip [1..] fs)
39− do compiled <- alreadyCompiled flags filePath
51+ where build1 :: Integer -> FilePath -> Build ()
52+ build1 num fname = do
53+ shouldCompile <- shouldBeCompiled fname
55+ then compile number fname
58+ where number = join ["[", show num, " of ", show total, "]"]
62+shouldBeCompiled :: FilePath -> Build Bool
63+shouldBeCompiled filePath = do
65+ let alreadyCompiled = liftIO $ do
66+ existsi <- doesFileExist (Utils.elmi flags filePath)
67+ existso <- doesFileExist (Utils.elmo flags filePath)
68+ return $ existsi && existso
70+ outDated = liftIO $ do
71+ tsrc <- getModificationTime filePath
72+ tint <- getModificationTime (Utils.elmo flags filePath)
75+ dependenciesUpdated = do
76+ eDeps <- liftIO . runErrorT $ Deps.readDeps filePath
40− case compiled of
77+ case eDeps of
41− False -> compile flags number interfaces filePath
78+ -- Should never actually reach here
42− True -> retrieve flags interfaces filePath
79+ Left err -> liftIO $ Print.failure err
43− where
80+ Right (_, deps) -> anyM wasCompiled deps
44− number = "[" ++ show moduleNum ++ " of " ++ show numModules ++ "]"
81+
46−
83+ in (not <$> alreadyCompiled) `orM` outDated `orM` dependenciesUpdated
47−alreadyCompiled :: Flag.Flags -> FilePath -> IO Bool
84+
48−alreadyCompiled flags filePath = do
85+wasCompiled :: String -> Build Bool
49− existsi <- doesFileExist (Utils.elmi flags filePath)
86+wasCompiled modul = maybe False fst . Map.lookup modul <$> get
50− existso <- doesFileExist (Utils.elmo flags filePath)
87+
51− if not existsi || not existso
88+-- Short-circuiting monadic (||)
52− then return False
89+infixr 2 `orM`
53− else do tsrc <- getModificationTime filePath
90+orM :: (Monad m) => m Bool -> m Bool -> m Bool
54− tint <- getModificationTime (Utils.elmo flags filePath)
91+orM m1 m2 = do b1 <- m1
55− return (tsrc <= tint)
92+ if b1
57−retrieve :: Flag.Flags -> Map.Map String M.ModuleInterface -> FilePath
94+ else m2
58− -> IO (String, M.ModuleInterface)
95+
59−retrieve flags interfaces filePath = do
96+anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
97+anyM f = foldr (orM . f) (return False)
99+retrieve :: FilePath -> Build ()
100+retrieve filePath = do
60− iface <- Interface.load (Utils.elmi flags filePath)
102+ iface <- liftIO $ Interface.load (Utils.elmi flags filePath)
61 case Interface.isValid filePath iface of
103 case Interface.isValid filePath iface of
62 Right (name, interface) ->
104 Right (name, interface) ->
105+ do binterfaces <- get
106+ let interfaces = snd <$> binterfaces
63− do when (Flag.print_types flags) (Print.interfaceTypes interfaces interface)
107+ liftIO $ when (Flag.print_types flags) (Print.interfaceTypes interfaces interface)
64− return (name, interface)
108+ update name interface False
65− Left err ->
110+ Left err -> liftIO $ Print.failure err
66− do hPutStrLn stderr err
111+
69−compile :: Flag.Flags -> String -> M.Interfaces -> FilePath
112+compile :: String -> FilePath -> Build ()
70− -> IO (String, M.ModuleInterface)
71−compile flags number interfaces filePath =
113+compile number filePath =
72− do source <- readFile filePath
116+ source <- liftIO $ readFile filePath
117+ let interfaces = snd <$> binterfaces
73− let name = getName source
118+ name = getName source
74− printStatus name
120+ printStatus name
76− createDirectoryIfMissing True (Flag.cache_dir flags)
121+ createDirectoryIfMissing True (Flag.cache_dir flags)
77− createDirectoryIfMissing True (Flag.build_dir flags)
122+ createDirectoryIfMissing True (Flag.build_dir flags)
79− metaModule <-
124+ metaModule <-
80− case Source.build (Flag.no_prelude flags) interfaces source of
125+ liftIO $ case Source.build (Flag.no_prelude flags) interfaces source of
81 Right modul -> return modul
126 Right modul -> return modul
82 Left errors -> do Print.errors errors
127 Left errors -> do Print.errors errors
83 exitFailure
128 exitFailure
85− when (Flag.print_types flags) $ Print.metaTypes interfaces metaModule
130+ liftIO $ when (Flag.print_types flags) $ Print.metaTypes interfaces metaModule
87− let intermediate = (name, Canonical.interface name $ M.metaToInterface metaModule)
132+ let newInters = Canonical.interface name $ M.metaToInterface metaModule
88− generateCache intermediate metaModule
133+ generateCache name newInters metaModule
89− return intermediate
134+ update name newInters True
92 getName source = case Parser.getModuleName source of
137 getName source = case Parser.getModuleName source of
@@ -98,8 +143,14 @@ compile flags number interfaces filePath =
98 , replicate (max 1 (20 - length name)) ' '
143 , replicate (max 1 (20 - length name)) ' '
99 , "( " ++ filePath ++ " )" ]
144 , "( " ++ filePath ++ " )" ]
101− generateCache intermediate metaModule = do
146+ generateCache name interfs metaModule = do
102− createDirectoryIfMissing True . dropFileName $ Utils.elmi flags filePath
149+ createDirectoryIfMissing True . dropFileName $ Utils.elmi flags filePath
103− writeFile (Utils.elmo flags filePath) (JS.generate metaModule)
150+ writeFile (Utils.elmo flags filePath) (JS.generate metaModule)
104− withBinaryFile (Utils.elmi flags filePath) WriteMode $ \handle ->
151+ withBinaryFile (Utils.elmi flags filePath) WriteMode $ \handle ->
105− L.hPut handle (Binary.encode intermediate)
152+ L.hPut handle (Binary.encode (name, interfs))
154+update :: String -> M.ModuleInterface -> Bool -> Build ()
155+update name inter wasUpdated = modify (Map.insert name (wasUpdated, inter))
156+ >> tell (Last . Just $ name)