{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
module Main where
import Control.Applicative
import Control.Monad
import Control.Monad.Writer.Strict
import qualified Data.Aeson as A
import Data.Bool (bool)
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.UTF8 as BU8
import qualified Data.Map as M
import Data.Text (Text)
import Data.Version (showVersion)
import qualified Language.PureScript as P
import Language.PureScript.Errors.JSON
import Language.PureScript.Make
import Options.Applicative as Opts
import qualified Paths_purescript as Paths
import qualified System.Console.ANSI as ANSI
import System.Exit (exitSuccess, exitFailure)
import System.FilePath.Glob (glob)
import System.IO (hSetEncoding, hPutStrLn, stdout, stderr, utf8)
import System.IO.UTF8 (readUTF8FileT)
data PSCMakeOptions = PSCMakeOptions
{ pscmInput :: [FilePath]
, pscmOutputDir :: FilePath
, pscmOpts :: P.Options
, pscmUsePrefix :: Bool
, pscmJSONErrors :: Bool
}
-- | Argumnets: verbose, use JSON, warnings, errors
printWarningsAndErrors :: Bool -> Bool -> P.MultipleErrors -> Either P.MultipleErrors a -> IO ()
printWarningsAndErrors verbose False warnings errors = do
cc <- bool Nothing (Just P.defaultCodeColor) <$> ANSI.hSupportsANSI stderr
let ppeOpts = P.defaultPPEOptions { P.ppeCodeColor = cc, P.ppeFull = verbose }
when (P.nonEmpty warnings) $
hPutStrLn stderr (P.prettyPrintMultipleWarnings ppeOpts warnings)
case errors of
Left errs -> do
hPutStrLn stderr (P.prettyPrintMultipleErrors ppeOpts errs)
exitFailure
Right _ -> return ()
printWarningsAndErrors verbose True warnings errors = do
hPutStrLn stderr . BU8.toString . B.toStrict . A.encode $
JSONResult (toJSONErrors verbose P.Warning warnings)
(either (toJSONErrors verbose P.Error) (const []) errors)
either (const exitFailure) (const (return ())) errors
compile :: PSCMakeOptions -> IO ()
compile PSCMakeOptions{..} = do
input <- globWarningOnMisses (unless pscmJSONErrors . warnFileTypeNotFound) pscmInput
when (null input && not pscmJSONErrors) $ do
hPutStrLn stderr "psc: No input files."
exitFailure
moduleFiles <- readInput input
(makeErrors, makeWarnings) <- runMake pscmOpts $ do
ms <- P.parseModulesFromFiles id moduleFiles
let filePathMap = M.fromList $ map (\(fp, P.Module _ _ mn _ _) -> (mn, Right fp)) ms
foreigns <- inferForeignModules filePathMap
let makeActions = buildMakeActions pscmOutputDir filePathMap foreigns pscmUsePrefix
P.make makeActions (map snd ms)
printWarningsAndErrors (P.optionsVerboseErrors pscmOpts) pscmJSONErrors makeWarnings makeErrors
exitSuccess
warnFileTypeNotFound :: String -> IO ()
warnFileTypeNotFound = hPutStrLn stderr . ("psc: No files found using pattern: " ++)
globWarningOnMisses :: (String -> IO ()) -> [FilePath] -> IO [FilePath]
globWarningOnMisses warn = concatMapM globWithWarning
where
globWithWarning pattern' = do
paths <- glob pattern'
when (null paths) $ warn pattern'
return paths
concatMapM f = fmap concat . mapM f
readInput :: [FilePath] -> IO [(FilePath, Text)]
readInput inputFiles = forM inputFiles $ \inFile -> (inFile, ) <$> readUTF8FileT inFile
inputFile :: Parser FilePath
inputFile = strArgument $
metavar "FILE"
<> help "The input .purs file(s)"
outputDirectory :: Parser FilePath
outputDirectory = strOption $
short 'o'
<> long "output"
<> Opts.value "output"
<> showDefault
<> help "The output directory"
noTco :: Parser Bool
noTco = switch $
long "no-tco"
<> help "Disable tail call optimizations"
noMagicDo :: Parser Bool
noMagicDo = switch $
long "no-magic-do"
<> help "Disable the optimization that overloads the do keyword to generate efficient code specifically for the Eff monad"
noOpts :: Parser Bool
noOpts = switch $
long "no-opts"
<> help "Skip the optimization phase"
comments :: Parser Bool
comments = switch $
short 'c'
<> long "comments"
<> help "Include comments in the generated code"
verboseErrors :: Parser Bool
verboseErrors = switch $
short 'v'
<> long "verbose-errors"
<> help "Display verbose error messages"
noPrefix :: Parser Bool
noPrefix = switch $
short 'p'
<> long "no-prefix"
<> help "Do not include comment header"
jsonErrors :: Parser Bool
jsonErrors = switch $
long "json-errors"
<> help "Print errors to stderr as JSON"
sourceMaps :: Parser Bool
sourceMaps = switch $
long "source-maps"
<> help "Generate source maps"
dumpCoreFn :: Parser Bool
dumpCoreFn = switch $
long "dump-corefn"
<> help "Dump the (functional) core representation of the compiled code at output/*/corefn.json"
options :: Parser P.Options
options = P.Options <$> noTco
<*> noMagicDo
<*> pure Nothing
<*> noOpts
<*> verboseErrors
<*> (not <$> comments)
<*> sourceMaps
<*> dumpCoreFn
pscMakeOptions :: Parser PSCMakeOptions
pscMakeOptions = PSCMakeOptions <$> many inputFile
<*> outputDirectory
<*> options
<*> (not <$> noPrefix)
<*> jsonErrors
main :: IO ()
main = do
hSetEncoding stdout utf8
hSetEncoding stderr utf8
execParser opts >>= compile
where
opts = info (version <*> helper <*> pscMakeOptions) infoModList
infoModList = fullDesc <> headerInfo <> footerInfo
headerInfo = header "psc - Compiles PureScript to Javascript"
footerInfo = footer $ "psc " ++ showVersion Paths.version
version :: Parser (a -> a)
version = abortOption (InfoMsg (showVersion Paths.version)) $ long "version" <> help "Show the version number" <> hidden