elm/compiler/Compiler.hs

215 lines
8.2 KiB
Haskell
Raw Normal View History

{-# LANGUAGE DeriveDataTypeable #-}
2012-04-19 06:32:10 +00:00
module Main where
import Control.Monad
import qualified Data.Map as Map
import qualified Data.List as List
2013-07-25 18:53:22 +00:00
import qualified Data.Binary as Binary
2012-10-03 07:17:09 +00:00
import Data.Version (showVersion)
2013-07-21 20:49:42 +00:00
import System.Console.CmdArgs hiding (program)
import System.Directory
import System.Exit
import System.FilePath
import System.IO
import GHC.Conc
import qualified Text.Blaze.Html.Renderer.Pretty as Pretty
import qualified Text.Blaze.Html.Renderer.String as Normal
import qualified Text.Jasmine as JS
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.ByteString.Lazy as L
2013-07-25 18:53:22 +00:00
import qualified Metadata.Prelude as Prelude
import qualified Transform.Canonicalize as Canonical
2013-06-14 05:59:14 +00:00
import SourceSyntax.Module
import Parse.Module (getModuleName)
import Initialize (buildFromSource, getSortedDependencies)
2013-06-14 05:59:14 +00:00
import Generate.JavaScript (jsModule)
import Generate.Html (createHtml, JSStyle(..), JSSource(..))
import Paths_Elm
import SourceSyntax.PrettyPrint (pretty, variable)
import Text.PrettyPrint as P
import qualified Type.Type as Type
import qualified Data.Traversable as Traverse
data Flags =
Flags { make :: Bool
, files :: [FilePath]
, runtime :: Maybe FilePath
, only_js :: Bool
, print_types :: Bool
2013-07-21 20:49:42 +00:00
, print_program :: Bool
, scripts :: [FilePath]
, no_prelude :: Bool
, minify :: Bool
, cache_dir :: FilePath
, build_dir :: FilePath
}
deriving (Data,Typeable,Show,Eq)
flags = Flags
{ make = False
&= help "automatically compile dependencies."
, files = def &= args &= typ "FILES"
, runtime = Nothing &= typFile
&= help "Specify a custom location for Elm's runtime system."
, only_js = False
&= help "Compile only to JavaScript."
, print_types = False
&= help "Print out infered types of top-level definitions."
2013-07-21 20:49:42 +00:00
, print_program = False
&= help "Print out an internal representation of a program."
, scripts = [] &= typFile
&= help "Load JavaScript files in generated HTML. Files will be included in the given order."
, no_prelude = False
&= help "Do not import Prelude by default, used only when compiling standard libraries."
, minify = False
2013-06-05 21:22:30 +00:00
&= help "Minify generated JavaScript and HTML"
, cache_dir = "cache" &= typFile
&= help "Directory for files cached to make builds faster. Defaults to cache/ directory."
, build_dir = "build" &= typFile
&= help "Directory for generated HTML and JS files. Defaults to build/ directory."
} &= help "Compile Elm programs to HTML, CSS, and JavaScript."
&= summary ("The Elm Compiler " ++ showVersion version ++ ", (c) Evan Czaplicki")
main :: IO ()
main = do setNumCapabilities =<< getNumProcessors
compileArgs =<< cmdArgs flags
compileArgs :: Flags -> IO ()
compileArgs flags =
2013-06-05 21:22:30 +00:00
case files flags of
[] -> putStrLn "Usage: elm [OPTIONS] [FILES]\nFor more help: elm --help"
fs -> mapM_ (build flags) fs
buildPath :: Flags -> FilePath -> String -> FilePath
buildPath flags filePath ext = build_dir flags </> replaceExtension filePath ext
cachePath :: Flags -> FilePath -> String -> FilePath
cachePath flags filePath ext = cache_dir flags </> replaceExtension filePath ext
2013-06-10 06:36:59 +00:00
elmo :: Flags -> FilePath -> FilePath
elmo flags filePath = cachePath flags filePath "elmo"
2013-06-10 06:36:59 +00:00
2013-07-31 14:42:31 +00:00
elmi :: Flags -> FilePath -> FilePath
elmi flags filePath = cachePath flags filePath "elmi"
2013-06-10 06:36:59 +00:00
buildFile :: Flags -> Int -> Int -> Interfaces -> FilePath -> IO (String,ModuleInterface)
buildFile flags moduleNum numModules interfaces filePath =
do compiled <- alreadyCompiled
case compiled of
False -> compile
True -> do
handle <- openBinaryFile (elmi flags filePath) ReadMode
bits <- L.hGetContents handle
let info :: (String, ModuleInterface)
info = Binary.decode bits
L.length bits `seq` hClose handle
return info
where
alreadyCompiled :: IO Bool
alreadyCompiled = do
existsi <- doesFileExist (elmi flags filePath)
existso <- doesFileExist (elmo flags filePath)
if not existsi || not existso
then return False
else do tsrc <- getModificationTime filePath
tint <- getModificationTime (elmo flags filePath)
return (tsrc < tint)
number :: String
number = "[" ++ show moduleNum ++ " of " ++ show numModules ++ "]"
compile :: IO (String,ModuleInterface)
compile = do
source <- readFile filePath
let name = case getModuleName source of
Just n -> n
Nothing -> "Main"
putStrLn $ concat [ number, " Compiling ", name
, replicate (max 1 (20 - length name)) ' '
, "( " ++ filePath ++ " )" ]
createDirectoryIfMissing True (cache_dir flags)
createDirectoryIfMissing True (build_dir flags)
metaModule <-
case buildFromSource (no_prelude flags) interfaces source of
2013-08-03 18:39:56 +00:00
Left errors -> do
mapM print (List.intersperse (P.text " ") errors)
exitFailure
Right modul -> do
2013-07-29 11:21:34 +00:00
case print_program flags of
False -> return ()
True -> print . pretty $ program modul
2013-07-22 12:41:55 +00:00
return modul
2013-07-21 20:49:42 +00:00
if print_types flags then printTypes metaModule else return ()
tipes <- Traverse.traverse Type.toSrcType (types metaModule)
let interface = Canonical.interface name $ ModuleInterface {
iTypes = tipes,
2013-07-26 13:06:35 +00:00
iAdts = datatypes metaModule,
iAliases = aliases metaModule
}
createDirectoryIfMissing True . dropFileName $ elmi flags filePath
handle <- openBinaryFile (elmi flags filePath) WriteMode
L.hPut handle (Binary.encode (name,interface))
hClose handle
writeFile (elmo flags filePath) (jsModule metaModule)
return (name,interface)
printTypes metaModule = do
putStrLn ""
forM_ (Map.toList $ types metaModule) $ \(n,t) -> do
pt <- Type.extraPretty t
print $ variable n <+> P.text ":" <+> pt
putStrLn ""
getRuntime :: Flags -> IO FilePath
getRuntime flags =
case runtime flags of
Just fp -> return fp
Nothing -> getDataFileName "elm-runtime.js"
build :: Flags -> FilePath -> IO ()
build flags rootFile = do
let noPrelude = no_prelude flags
files <- if make flags then getSortedDependencies noPrelude rootFile else return [rootFile]
let ifaces = if noPrelude then Map.empty else Prelude.interfaces
(moduleName, interfaces) <- buildFiles flags (length files) ifaces "" files
js <- foldM appendToOutput "" files
case only_js flags of
True -> do
2013-06-05 21:22:30 +00:00
putStr "Generating JavaScript ... "
writeFile (buildPath flags rootFile "js") (genJs js)
putStrLn "Done"
False -> do
2013-06-05 21:22:30 +00:00
putStr "Generating HTML ... "
runtime <- getRuntime flags
let html = genHtml $ createHtml runtime (takeBaseName rootFile) (sources js) moduleName ""
htmlFile = buildPath flags rootFile "html"
createDirectoryIfMissing True (takeDirectory htmlFile)
writeFile htmlFile html
putStrLn "Done"
where
appendToOutput :: String -> FilePath -> IO String
appendToOutput js filePath =
2013-06-10 06:36:59 +00:00
do src <- readFile (elmo flags filePath)
return (src ++ js)
genHtml = if minify flags then Normal.renderHtml else Pretty.renderHtml
genJs = if minify flags then BS.unpack . JS.minify . BS.pack else id
sources js = map Link (scripts flags) ++
[ Source (if minify flags then Minified else Readable) js ]
buildFiles :: Flags -> Int -> Interfaces -> String -> [FilePath] -> IO (String, Interfaces)
buildFiles _ _ interfaces moduleName [] = return (moduleName, interfaces)
buildFiles flags numModules interfaces _ (filePath:rest) = do
(name,interface) <- buildFile flags (numModules - length rest) numModules interfaces filePath
let interfaces' = Map.insert name interface interfaces
buildFiles flags numModules interfaces' name rest