packages feed

pec-0.2.0: src/Pec.hs

{-# OPTIONS -Wall -fno-warn-incomplete-patterns #-}
{-# LANGUAGE DeriveDataTypeable #-}

-- The pec embedded compiler
-- Copyright 2011-2012, Brett Letner

module Main(main) where

import Control.Monad
import Data.List
import Development.Shake hiding (getDirectoryContents)
import Development.Shake.FilePath
import Distribution.Text
import Grm.Prims
import Paths_pec
import Pec.PUtil
import System.Console.CmdArgs
import System.Directory hiding (readable)

data Args = Args
  { targets :: [String]
  , idir :: [FilePath]
  , lib :: [String]
  , ldir :: [FilePath]
  , march :: Arch
  , readable :: Bool
  } deriving (Show, Data, Typeable)

argsDesc :: Args
argsDesc = Args
  { targets = def &= args
  , idir = def &= typDir &= help "import directory"
  , lib = def &= help "library"
  , ldir = def &= typDir &= name "L" &= help "library directory"
  , march = def &= help "arch to build (C or LLVM)"
  , readable = def &= help "generate human readable C (experimental)"
  } &= summary summry &= program prog

summry :: String
summry = prog ++ " v" ++ display version ++ ", " ++ copyright

prog :: String
prog = "pec"

buildDir :: String
buildDir = ".build"

mkTarget :: String -> String
mkTarget t =
  if takeExtension t == ".pec" then replaceExtension t "exe" else t

getExeFiles :: IO [FilePath]
getExeFiles = liftM (filter isExeFile) (getDirectoryContents ".")

isExeFile :: FilePath -> Bool
isExeFile = (==) ".exe" . takeExtension

rmrf :: FilePath -> IO ()
rmrf fn = do
  r <- doesDirectoryExist fn
  when r $ removeDirectoryRecursive fn
  
main :: IO ()
main = do
  a <- cmdArgs argsDesc
  libDir <- liftM takeDirectory $ getDataFileName "lib/Prelude.pec"
  let idirs = ["."] ++ idir a ++ [libDir]

  when ("clean" `elem` targets a) $ do
    rmrf buildDir
    getExeFiles >>= mapM_ removeFile
    
  let ts = map mkTarget $ targets a \\ ["clean","run"]

  let do_runghc outFn = do
        let hsFn = dropExtension outFn ++ "_.hs"
        need [hsFn]
        system' "runghc"
          [ "-i" ++ buildDir, hsFn
          , "--march=" ++ show (march a)
          , "--readable=" ++ show (readable a)]
      
  -- shake shakeOptions{ shakeVerbosity = Diagnostic } $ do
  shake shakeOptions $ do

    want ts

    "//*.exe" *> \outFn -> case march a of
      C -> do
        let depFn = buildPath $ replaceExtension outFn "dep"
        need [depFn]
        xs <- loadFileDeps depFn
        let fns = [ replaceExtension x "o" | x <- xs ]
        need fns
        system' "gcc" $
          ["-g", "-O2", "-Wall", "-Wextra", "-o", outFn] ++
          ["-L" ++ l | l <- ldir a] ++ ["-l" ++ l | l <- lib a] ++ fns
      LLVM -> do
        let fn = buildPath $ replaceExtension outFn "s"
        need [fn]
        system' "gcc" [ "-O2", "-o", outFn, fn ]

    "//*.s" *> \outFn -> do
      let fn = replaceExtension outFn "opt"
      need [fn]
      system' "llc" [ "-o", outFn, fn ]

    "//*.opt" *> \outFn -> do
      let fn = replaceExtension outFn "bca"
      need [fn]
      system' "opt" [ "-o", outFn, "-std-compile-opts", "-std-link-opts", fn ]
  
    "//*.bca" *> \outFn -> do
      let depFn = replaceExtension outFn "dep"
      need [depFn]
      xs <- loadFileDeps depFn
      let fns = [ replaceExtension x "bc" | x <- xs ]
      need fns
      system' "llvm-link" $ [ "-o", outFn ] ++ fns

    "//*.o" *> \outFn -> do
      let fn = replaceExtension outFn "c"
      need [fn, replaceExtension outFn "h"]
      system' "gcc" ["-g", "-o", outFn, "-c", fn ]
      
    "//*.c" *> do_runghc
    "//*.h" *> do_runghc
    "//*.ll" *> do_runghc

    "//*.bc" *> \outFn -> do
      let fn = replaceExtension outFn "ll"
      need [fn]
      system' "llvm-as" ["-o", outFn, fn ]

    "//*_.hs" *> \hsOut -> do
      need [(init $ dropExtension hsOut) ++ ".dep"]

    "//*.dep" *> \depOut -> do
      let fn = dropDirectory1
            (und_to_path (dropExtension depOut) ++ ".pec")
      mFn <- liftIO $ findFile idirs fn
      case mFn of
        Nothing -> error $ "unknown file:" ++ fn
        Just fn1 -> do
          need [fn1] -- BAL: needed?
          system' "pecgen" ["-d", buildDir, fn1]
          (xs,ys) <- liftIO $ readFileDeps depOut
          need [ buildPath $ x ++ ".dep" | x <- xs ]
          need [ buildPath $ "Cnt" ++ show y ++ ".hs" | y <- ys ]

    "//Cnt*.hs" *> \outFn -> system' "pecgencnt" [outFn]
      
    when ("run" `elem` targets a) $ action $ do -- BAL: doesn't work under windows
      pwd <- liftIO getCurrentDirectory
      mapM_ (\fn -> system' (pwd </> fn) []) $ filter isExeFile ts

loadFileDeps :: FilePath -> Action [String]
loadFileDeps fn0 = loop [] [fn0]
  where
  loop xs [] = return xs
  loop xs (y:ys)
    | y `elem` xs = loop xs ys
    | otherwise = do
        (zs,_) <- liftIO $ readFileDeps y
        loop (y : xs) ([ buildPath $ z ++ ".dep" | z <- zs ] ++ ys)
                     
buildPath :: FilePath -> FilePath
buildPath x = buildDir </> x