module Language.PureScript.Make
(
rebuildModule
, make
, inferForeignModules
, module Monad
, module Actions
) where
import Prelude.Compat
import Control.Concurrent.Lifted as C
import Control.Monad hiding (sequence)
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.IO.Class
import Control.Monad.Supply
import Control.Monad.Trans.Control (MonadBaseControl(..))
import Control.Monad.Writer.Class (MonadWriter(..))
import Data.Aeson (encode)
import Data.Function (on)
import Data.Foldable (for_)
import Data.List (foldl', sortBy)
import qualified Data.List.NonEmpty as NEL
import Data.Maybe (fromMaybe)
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text as T
import Language.PureScript.AST
import Language.PureScript.Crash
import qualified Language.PureScript.CST as CST
import qualified Language.PureScript.Docs.Convert as Docs
import Language.PureScript.Environment
import Language.PureScript.Errors
import Language.PureScript.Externs
import Language.PureScript.Linter
import Language.PureScript.ModuleDependencies
import Language.PureScript.Names
import Language.PureScript.Renamer
import Language.PureScript.Sugar
import Language.PureScript.TypeChecker
import Language.PureScript.Make.BuildPlan
import qualified Language.PureScript.Make.BuildPlan as BuildPlan
import Language.PureScript.Make.Actions as Actions
import Language.PureScript.Make.Monad as Monad
import qualified Language.PureScript.CoreFn as CF
import System.Directory (doesFileExist)
import System.FilePath (replaceExtension)
rebuildModule
:: forall m
. (Monad m, MonadBaseControl IO m, MonadError MultipleErrors m, MonadWriter MultipleErrors m)
=> MakeActions m
-> [ExternsFile]
-> Module
-> m ExternsFile
rebuildModule MakeActions{..} externs m@(Module _ _ moduleName _ _) = do
progress $ CompilingModule moduleName
let env = foldl' (flip applyExternsFileToEnvironment) initEnvironment externs
withPrim = importPrim m
lint withPrim
((Module ss coms _ elaborated exps, env'), nextVar) <- runSupplyT 0 $ do
desugar externs [withPrim] >>= \case
[desugared] -> runCheck' (emptyCheckState env) $ typeCheckModule desugared
_ -> internalError "desugar did not return a singleton"
(deguarded, nextVar') <- runSupplyT nextVar $ do
desugarCaseGuards elaborated
regrouped <- createBindingGroups moduleName . collapseBindingGroups $ deguarded
let mod' = Module ss coms moduleName regrouped exps
corefn = CF.moduleToCoreFn env' mod'
optimized = CF.optimizeCoreFn corefn
[renamed] = renameInModules [optimized]
exts = moduleToExternsFile mod' env'
ffiCodegen renamed
let docs = case Docs.convertModule externs env' m of
Left errs -> internalError $
"Failed to produce docs for " ++ T.unpack (runModuleName moduleName)
++ "; details:\n" ++ prettyPrintMultipleErrors defaultPPEOptions errs
Right d -> d
evalSupplyT nextVar' . codegen renamed docs . encode $ exts
return exts
make :: forall m. (Monad m, MonadBaseControl IO m, MonadError MultipleErrors m, MonadWriter MultipleErrors m)
=> MakeActions m
-> [CST.PartialResult Module]
-> m [ExternsFile]
make ma@MakeActions{..} ms = do
checkModuleNames
(sorted, graph) <- sortModules (moduleSignature . CST.resPartial) ms
buildPlan <- BuildPlan.construct ma (sorted, graph)
let toBeRebuilt = filter (BuildPlan.needsRebuild buildPlan . getModuleName . CST.resPartial) sorted
for_ toBeRebuilt $ \m -> fork $ do
let moduleName = getModuleName . CST.resPartial $ m
let deps = fromMaybe (internalError "make: module not found in dependency graph.") (lookup moduleName graph)
buildModule buildPlan moduleName
(spanName . getModuleSourceSpan . CST.resPartial $ m)
(importPrim <$> CST.resFull m)
(deps `inOrderOf` map (getModuleName . CST.resPartial) sorted)
errors <- BuildPlan.collectErrors buildPlan
unless (null errors) $ throwError (mconcat errors)
results <- BuildPlan.collectResults buildPlan
let lookupResult mn = fromMaybe (internalError "make: module not found in results") (M.lookup mn results)
return (map (lookupResult . getModuleName . CST.resPartial) sorted)
where
checkModuleNames :: m ()
checkModuleNames = checkNoPrim *> checkModuleNamesAreUnique
checkNoPrim :: m ()
checkNoPrim =
for_ ms $ \m ->
let mn = getModuleName $ CST.resPartial m
in when (isBuiltinModuleName mn) $
throwError
. errorMessage' (getModuleSourceSpan $ CST.resPartial m)
$ CannotDefinePrimModules mn
checkModuleNamesAreUnique :: m ()
checkModuleNamesAreUnique =
for_ (findDuplicates (getModuleName . CST.resPartial) ms) $ \mss ->
throwError . flip foldMap mss $ \ms' ->
let mn = getModuleName . CST.resPartial . NEL.head $ ms'
in errorMessage'' (fmap (getModuleSourceSpan . CST.resPartial) ms') $ DuplicateModule mn
findDuplicates :: Ord b => (a -> b) -> [a] -> Maybe [NEL.NonEmpty a]
findDuplicates f xs =
case filter ((> 1) . length) . NEL.groupBy ((==) `on` f) . sortBy (compare `on` f) $ xs of
[] -> Nothing
xss -> Just xss
inOrderOf :: (Ord a) => [a] -> [a] -> [a]
inOrderOf xs ys = let s = S.fromList xs in filter (`S.member` s) ys
buildModule :: BuildPlan -> ModuleName -> FilePath -> Either (NEL.NonEmpty CST.ParserError) Module -> [ModuleName] -> m ()
buildModule buildPlan moduleName fp mres deps = flip catchError (complete Nothing . Just) $ do
m <- CST.unwrapParserError fp mres
mexterns <- fmap unzip . sequence <$> traverse (getResult buildPlan) deps
case mexterns of
Just (_, externs) -> do
(exts, warnings) <- listen $ rebuildModule ma externs m
complete (Just (warnings, exts)) Nothing
Nothing -> complete Nothing Nothing
where
complete :: Maybe (MultipleErrors, ExternsFile) -> Maybe MultipleErrors -> m ()
complete = BuildPlan.markComplete buildPlan moduleName
inferForeignModules
:: forall m
. MonadIO m
=> M.Map ModuleName (Either RebuildPolicy FilePath)
-> m (M.Map ModuleName FilePath)
inferForeignModules =
fmap (M.mapMaybe id) . traverse inferForeignModule
where
inferForeignModule :: Either RebuildPolicy FilePath -> m (Maybe FilePath)
inferForeignModule (Left _) = return Nothing
inferForeignModule (Right path) = do
let jsFile = replaceExtension path "js"
exists <- liftIO $ doesFileExist jsFile
if exists
then return (Just jsFile)
else return Nothing