git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Merge branch 'make'
commitec1ee671f4295eaee3d89c7e93e4ddb29e380423 authorPhil Freeman <paf31@cantab.net> dateSat 15 Mar 2014, 16:33 (13 years ago) parentc615fb25f5f290
4 files changed+186−34
psc/Main.hs+71−25 purescript.cabal+1−1 src/Language/PureScript.hs+105−5 src/Language/PureScript/TypeChecker/Monad.hs+9−3
psc/Main.hs+71−25
@@ -12,17 +12,21 @@
1212--
1313-----------------------------------------------------------------------------
1414
15+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
16+
1517module Main where
1618
1719import Control.Applicative
18−import Control.Monad (forM)
20+import Control.Monad.Error
1921
2022import Data.Version (showVersion)
2123
2224import System.Console.CmdTheLine
23−import System.Directory (createDirectoryIfMissing)
25+import System.Directory
26+ (doesFileExist, getModificationTime, createDirectoryIfMissing)
2427import System.FilePath (takeDirectory)
2528import System.Exit (exitSuccess, exitFailure)
29+import System.IO.Error (tryIOError)
2630
2731import Text.Parsec (ParseError)
2832
@@ -33,35 +37,73 @@ import qualified System.IO.UTF8 as U
3337preludeFilename :: IO FilePath
3438preludeFilename = Paths.getDataFileName "prelude/prelude.purs"
3539
36−readInput :: Maybe [FilePath] -> IO (Either ParseError [P.Module])
37−readInput Nothing = P.runIndentParser "" P.parseModules <$> getContents
38−readInput (Just input) = fmap (fmap concat . sequence) $ forM input $ \inputFile -> do
40+readInput :: Maybe [FilePath] -> IO (Either ParseError [(FilePath, P.Module)])
41+readInput Nothing = do
42+ text <- getContents
43+ return $ map ((,) undefined) <$> P.runIndentParser "" P.parseModules text
44+readInput (Just input) = fmap collect $ forM input $ \inputFile -> do
3945 text <- U.readFile inputFile
40− return $ P.runIndentParser inputFile P.parseModules text
41−
42−compile :: P.Options -> Maybe [FilePath] -> Maybe FilePath -> Maybe FilePath -> IO ()
43−compile opts input output externs = do
46+ return $ (inputFile, P.runIndentParser inputFile P.parseModules text)
47+ where
48+ collect :: [(FilePath, Either ParseError [P.Module])] -> Either ParseError [(FilePath, P.Module)]
49+ collect = fmap concat . sequence . map (\(fp, e) -> fmap (map ((,) fp)) e)
50+
51+newtype Make a = Make { unMake :: ErrorT String IO a } deriving (Functor, Monad, MonadIO, MonadError String)
52+
53+runMake :: Make a -> IO (Either String a)
54+runMake = runErrorT . unMake
55+
56+makeIO :: IO a -> Make a
57+makeIO = Make . ErrorT . fmap (either (Left . show) Right) . tryIOError
58+
59+instance P.MonadMake Make where
60+ getTimestamp path = makeIO $ do
61+ exists <- doesFileExist path
62+ case exists of
63+ True -> Just <$> getModificationTime path
64+ False -> return Nothing
65+ readTextFile path = makeIO $ do
66+ U.putStrLn $ "Reading " ++ path
67+ U.readFile path
68+ writeTextFile path text = makeIO $ do
69+ mkdirp path
70+ U.putStrLn $ "Writing " ++ path
71+ U.writeFile path text
72+ liftError = either throwError return
73+
74+compile :: Bool -> P.Options -> Maybe [FilePath] -> Maybe FilePath -> Maybe FilePath -> IO ()
75+compile makeMode opts input output externs = do
4476 modules <- readInput input
4577 case modules of
4678 Left err -> do
4779 U.print err
4880 exitFailure
4981 Right ms ->
50− case P.compile opts ms of
51− Left err -> do
52− U.putStrLn err
53− exitFailure
54− Right (js, exts, _) -> do
55− case output of
56− Just path -> mkdirp path >> U.writeFile path js
57− Nothing -> U.putStrLn js
58− case externs of
59− Just path -> mkdirp path >> U.writeFile path exts
60− Nothing -> return ()
61− exitSuccess
62− where
63− mkdirp :: FilePath -> IO ()
64− mkdirp = createDirectoryIfMissing True . takeDirectory
82+ case makeMode of
83+ True -> do
84+ e <- runMake $ P.make opts ms
85+ case e of
86+ Left err -> do
87+ U.putStrLn err
88+ exitFailure
89+ Right _ -> do
90+ exitSuccess
91+ False ->
92+ case P.compile opts (map snd ms) of
93+ Left err -> do
94+ U.putStrLn err
95+ exitFailure
96+ Right (js, exts, _) -> do
97+ case output of
98+ Just path -> mkdirp path >> U.writeFile path js
99+ Nothing -> U.putStrLn js
100+ case externs of
101+ Just path -> mkdirp path >> U.writeFile path exts
102+ Nothing -> return ()
103+ exitSuccess
104+
105+mkdirp :: FilePath -> IO ()
106+mkdirp = createDirectoryIfMissing True . takeDirectory
65107
66108useStdIn :: Term Bool
67109useStdIn = value . flag $ (optInfo [ "s", "stdin" ])
@@ -103,6 +145,10 @@ noOpts :: Term Bool
103145noOpts = value $ flag $ (optInfo [ "no-opts" ])
104146 { optDoc = "Skip the optimization phase." }
105147
148+make :: Term Bool
149+make = value $ flag $ (optInfo [ "make" ])
150+ { optDoc = "Run in make mode" }
151+
106152browserNamespace :: Term String
107153browserNamespace = value $ opt "PS" $ (optInfo [ "browser-namespace" ])
108154 { optDoc = "Specify the namespace that PureScript modules will be exported to when running in the browser." }
@@ -126,7 +172,7 @@ stdInOrInputFiles prelude = combine <$> useStdIn <*> (not <$> noPrelude) <*> inp
126172 combine True _ _ = Nothing
127173
128174term :: FilePath -> Term (IO ())
129−term prelude = compile <$> options <*> stdInOrInputFiles prelude <*> outputFile <*> externsFile
175+term prelude = compile <$> make <*> options <*> stdInOrInputFiles prelude <*> outputFile <*> externsFile
130176
131177termInfo :: TermInfo
132178termInfo = defTI
purescript.cabal+1−1
@@ -21,7 +21,7 @@ library
2121 build-depends: base >=4 && <5, cmdtheline -any, containers -any,
2222 directory -any, filepath -any, mtl -any, parsec -any, syb -any,
2323 transformers -any, utf8-string -any, pattern-arrows -any, monad-unify >= 0.2 && < 0.3,
24− xdg-basedir -any
24+ xdg-basedir -any, time -any
2525 if (!os(windows))
2626 build-depends: unix -any
2727 exposed-modules: Data.Generics.Extras
src/Language/PureScript.hs+105−5
@@ -13,7 +13,7 @@
1313--
1414-----------------------------------------------------------------------------
1515
16−module Language.PureScript (module P, compile) where
16+module Language.PureScript (module P, compile, MonadMake(..), make) where
1717
1818import Language.PureScript.Values as P
1919import Language.PureScript.Types as P
@@ -34,10 +34,12 @@ import Language.PureScript.DeadCodeElimination as P
3434import qualified Language.PureScript.Constants as C
3535
3636import Data.List (intercalate)
37−import Data.Maybe (mapMaybe)
37+import Data.Time.Clock
38+import Data.Maybe (fromMaybe, mapMaybe)
3839import Control.Monad.State.Lazy
3940import Control.Applicative ((<$>), (<*>), pure)
4041import qualified Data.Map as M
42+import System.FilePath (pathSeparator)
4143
4244-- |
4345-- Compile a collection of modules
@@ -72,12 +74,110 @@ compile opts ms = do
7274 let modulesToCodeGen = if null codeGenModules then elim else filter (\(Module mn _ _) -> mn `elem` codeGenModules) elim
7375 let js = mapMaybe (flip (moduleToJs opts) env) modulesToCodeGen
7476 let exts = intercalate "\n" . map (`moduleToPs` env) $ modulesToCodeGen
75− js' <- case mainModuleIdent of
77+ js' <- generateMain env opts js
78+ return (prettyPrintJS [wrapExportsContainer opts js'], exts, env)
79+ where
80+ mainModuleIdent = moduleNameFromString <$> optionsMain opts
81+
82+generateMain :: Environment -> Options -> [JS] -> Either String [JS]
83+generateMain env opts js =
84+ case moduleNameFromString <$> optionsMain opts of
7685 Just mmi -> do
7786 when ((mmi, Ident C.main) `M.notMember` names env) $
7887 Left $ show mmi ++ "." ++ C.main ++ " is undefined"
7988 return $ js ++ [JSApp (JSAccessor C.main (JSAccessor (moduleNameToJs mmi) (JSVar C._ps))) []]
8089 _ -> return js
81− return (prettyPrintJS [wrapExportsContainer opts js'], exts, env)
90+
91+-- |
92+-- A type class which collects the IO actions we need to be able to run in "make" mode
93+--
94+class MonadMake m where
95+ -- |
96+ -- Get a file timestamp
97+ --
98+ getTimestamp :: FilePath -> m (Maybe UTCTime)
99+
100+ -- |
101+ -- Read a file as a string
102+ --
103+ readTextFile :: FilePath -> m String
104+
105+ -- |
106+ -- Write a text file
107+ --
108+ writeTextFile :: FilePath -> String -> m ()
109+
110+ -- |
111+ -- Report an error
112+ --
113+ liftError :: Either String a -> m a
114+
115+
116+-- |
117+-- Compiles in "make" mode, compiling each module separately to a js files and an externs file
118+--
119+-- If timestamps have not changed, the externs file can be used to provide the module's types without
120+-- having to typecheck the module again.
121+--
122+make :: (Functor m, Monad m, MonadMake m) => Options -> [(FilePath, Module)] -> m ()
123+make opts ms = do
124+ let filePathMap = M.fromList (map (\(fp, (Module mn _ _)) -> (mn, fp)) ms)
125+
126+ sorted <- liftError $ sortModules (map snd ms)
127+
128+ marked <- forM sorted $ \m@(Module moduleName' _ _) -> do
129+ let filePath = toFileName moduleName'
130+
131+ jsFile = "js" ++ pathSeparator : filePath ++ ".js"
132+ externsFile = "externs" ++ pathSeparator : filePath ++ ".externs"
133+ inputFile = fromMaybe (error "Input file is undefined in make") $ M.lookup moduleName' filePathMap
134+
135+ jsTimestamp <- getTimestamp jsFile
136+ externsTimestamp <- getTimestamp externsFile
137+ inputTimestamp <- getTimestamp inputFile
138+
139+ case inputTimestamp < min jsTimestamp externsTimestamp of
140+ True -> do
141+ externs <- readTextFile externsFile
142+ externsModules <- liftError . either (Left . show) Right $ P.runIndentParser externsFile P.parseModules externs
143+ case externsModules of
144+ [m'@(Module moduleName'' _ _)] | moduleName' == moduleName'' -> return (True, m')
145+ _ -> liftError . Left $ "Externs file " ++ externsFile ++ " was invalid"
146+ False -> return (False, m)
147+
148+ desugared <- liftError $ zip (map fst marked) <$> desugar (map snd marked)
149+
150+ go initEnvironment desugared
151+
82152 where
83− mainModuleIdent = moduleNameFromString <$> optionsMain opts
153+ go :: (Functor m, Monad m, MonadMake m) => Environment -> [(Bool, Module)] -> m ()
154+ go _ [] = return ()
155+ go env ((True, Module moduleName' typings _) : ms') = do
156+ (_, env') <- liftError . runCheck' env $ do
157+ modify (\s -> s { checkCurrentModule = Just moduleName' })
158+ typeCheckAll Nothing moduleName' typings
159+
160+ go env' ms'
161+ go env ((False, Module moduleName' decls exps) : ms') = do
162+ let filePath = toFileName moduleName'
163+ jsFile = "js" ++ pathSeparator : filePath ++ ".js"
164+ externsFile = "externs" ++ pathSeparator : filePath ++ ".externs"
165+
166+ (elaborated, env') <- liftError . runCheck' env $ do
167+ modify (\s -> s { checkCurrentModule = Just moduleName' })
168+ typeCheckAll Nothing moduleName' decls
169+
170+ regrouped <- liftError . createBindingGroups moduleName' . collapseBindingGroups $ elaborated
171+
172+ let mod' = Module moduleName' regrouped exps
173+ js = moduleToJs opts mod' env'
174+ exts = moduleToPs mod' env'
175+ js' = maybe "" (prettyPrintJS . return . wrapExportsContainer opts . return) js
176+
177+ writeTextFile jsFile js'
178+ writeTextFile externsFile exts
179+
180+ go env' ms'
181+
182+ toFileName :: ModuleName -> FilePath
183+ toFileName (ModuleName ps) = intercalate [pathSeparator] . map runProperName $ ps
src/Language/PureScript/TypeChecker/Monad.hs+9−3
@@ -153,11 +153,17 @@ modifyEnv :: (MonadState CheckState m) => (Environment -> Environment) -> m ()
153153modifyEnv f = modify (\s -> s { checkEnv = f (checkEnv s) })
154154
155155-- |
156−-- Run a computation in the Check monad, failing with an error, or succeeding with a return value and the final @Environment@.
156+-- Run a computation in the Check monad, starting with an empty @Environment@
157157--
158158runCheck :: Check a -> Either String (a, Environment)
159−runCheck c = do
160− (a, s) <- flip runStateT (CheckState initEnvironment 0 0 Nothing) $ unCheck c
159+runCheck = runCheck' initEnvironment
160+
161+-- |
162+-- Run a computation in the Check monad, failing with an error, or succeeding with a return value and the final @Environment@.
163+--
164+runCheck' :: Environment -> Check a -> Either String (a, Environment)
165+runCheck' env c = do
166+ (a, s) <- flip runStateT (CheckState env 0 0 Nothing) $ unCheck c
161167 return (a, checkEnv s)
162168
163169-- |