packages feed

husk-scheme-3.13: hs-src/Compiler/huskc.hs

{- |
Module      : Main
Copyright   : Justin Ethier
Licence     : MIT (see LICENSE in the distribution)

Maintainer  : github.com/justinethier
Portability : portable

A front-end for the husk compiler
-}

module Main where
import Paths_husk_scheme
import Language.Scheme.Compiler
import Language.Scheme.Compiler.Types
import qualified Language.Scheme.Core
import Language.Scheme.Types     -- Scheme data types
import Language.Scheme.Variables -- Scheme variable operations
import Control.Monad.Error
import System.Cmd (system)
import System.Console.GetOpt
import System.FilePath (dropExtension)
import System.Environment
import System.Exit (ExitCode (..), exitWith, exitFailure)
import System.IO

main :: IO ()
main = do 

  -- Read command line args and process options
  args <- getArgs
  let (actions, nonOpts, msgs) = getOpt Permute options args
  opts <- foldl (>>=) (return defaultOptions) actions
  let Options {optOutput = output, optLibs = lib, optDynamic = dynamic, optCustomOptions = extra, optSchemeRev = langrev} = opts

  if null nonOpts
     then showUsage
     else do
        let inFile = nonOpts !! 0
            outHaskell = (dropExtension inFile) ++ ".hs"
            outExec = case output of
              Just inFile -> inFile
              Nothing -> dropExtension inFile
            extraOpts = case extra of
              Just args -> args
              Nothing -> ""
-- TODO: pass language revision
        process inFile outHaskell outExec lib dynamic extraOpts langrev

-- 
-- For an explanation of the command line options code, see:
-- http://leiffrenzel.de/papers/commandline-options-in-haskell.html
--

-- |Data type to handle command line options that take parameters
data Options = Options {
    optOutput :: Maybe String, -- Executable file to write
    optLibs :: Bool, -- Debug flag, whether to compile standard libraries
    optDynamic :: Bool, -- Flag for dynamic linking of compiled executable
    optCustomOptions :: Maybe String, -- Custom options to ghc
    optSchemeRev :: String -- Scheme Language version
    }

-- |Default values for the command line options
defaultOptions :: Options
defaultOptions = Options {
    optOutput = Nothing,
    optLibs = True,
    optDynamic = False,
    optCustomOptions = Nothing, 
    optSchemeRev = "5"
    }

-- |Command line options
options :: [OptDescr (Options -> IO Options)]
options = [
  Option ['r'] ["revision"] (ReqArg writeRxRSVersion "Scheme") "scheme RxRS version",
  Option ['V'] ["version"] (NoArg showVersion) "show version number",
  Option ['h', '?'] ["help"] (NoArg showHelp) "show usage information",
  Option ['o'] ["output"] (ReqArg writeExec "FILE") "output file to write",
  Option ['d'] ["dynamic"] (NoArg getDynamic) "use dynamic linking for the compiled executable",
  Option ['x'] ["extra"] (ReqArg getExtraArgs "Args") "extra arguments to ghc",

  Option [] ["debug"] (NoArg showDebug) "show debug information",
  Option [] ["nolibs"] (NoArg getNoLibs) "a DEBUG option to use interpreted libraries instead of compiling them"
  ]

writeRxRSVersion arg opt = return opt { optSchemeRev = arg }

-- |Determine executable file to write. 
--  This version just takes a name from the command line option
writeExec arg opt = return opt { optOutput = Just arg }

getNoLibs opt = return opt { optLibs = False }

getDynamic opt = return opt { optDynamic = True }

getExtraArgs arg opt = return opt { optCustomOptions = Just arg }

-- TODO: would nice to have this as well as a 'real' usage printout, perhaps via --help

-- |Print a usage message
showUsage :: IO ()
showUsage = do
  putStrLn "huskc: no input files"

showHelp :: Options -> IO Options
showHelp _ = do
  putStrLn "Usage: huskc [options] file"
  putStrLn ""
  putStrLn "  Options:"
  putStrLn ""
  putStrLn "  --help                Display this information"
  putStrLn "  --version             Display husk version information"
  putStrLn "  --revision rev        Specify the scheme revision to use:"
  putStrLn ""
  putStrLn "                          5 - r5rs (default)"
  putStrLn "                          7 - r7rs small"
  putStrLn ""
  putStrLn "  --output filename     Write executable to the given filename"
  putStrLn "  --dynamic             Use dynamic Haskell libraries, if available"
  putStrLn "                        (Requires libraries built via --enable-shared)"
  putStrLn "  --extra args          Pass extra arguments directly to ghc"
  putStrLn ""
  exitWith ExitSuccess

-- |Print debug information
showDebug :: Options -> IO Options
showDebug _ = do
  stdlib <- getDataFileName "lib/stdlib.scm"
  putStrLn $ "stdlib: " ++ stdlib
  putStrLn ""
  exitWith ExitSuccess

-- |Print version information
showVersion :: Options -> IO Options
showVersion _ = do
  Language.Scheme.Core.showBanner
  exitWith ExitSuccess

-- |High level code to compile the given file
process :: String -> String -> String -> Bool -> Bool -> String -> String -> IO ()
process inFile outHaskell outExec libs dynamic extraArgs langrev = do
  env <- case langrev of
            "7" -> Language.Scheme.Core.r7rsEnv'
            _ -> Language.Scheme.Core.r5rsEnv'
  stdlib <- getDataFileName "lib/stdlib.scm"
  srfi55 <- getDataFileName "lib/srfi/srfi-55.scm" -- (require-extension)

  let stdlibArg = if libs
                     then Just stdlib
                     else Nothing

  result <- (Language.Scheme.Core.runIOThrows $ liftM show $ compileSchemeFile env stdlibArg srfi55 inFile outHaskell langrev)
  case result of
   Just errMsg -> putStrLn errMsg
   _ -> compileHaskellFile outHaskell outExec dynamic extraArgs

-- |Compile a scheme file to haskell
compileSchemeFile :: Env -> Maybe String -> String -> String -> String -> String -> IOThrowsError LispVal
compileSchemeFile env stdlib srfi55 filename outHaskell langrev = do
  let conv :: LispVal -> String
      conv (String s) = s
      compileLibraries = case stdlib of
        Just _ -> True
        _ -> False

  -- TODO: clean this up later
  --moduleFile <- liftIO $ getDataFileName "lib/modules.scm"

  (String nextFunc, libsC, libSrfi55C, libModules) <- case (stdlib, langrev) of
    (Just stdlib', "5") -> do
      -- TODO: it is only temporary to compile the standard library each time. It should be 
      --       precompiled and just added during the ghc compilation
      libsC <- compileLisp env stdlib' "run" (Just "exec55")
      libSrfi55C <- compileLisp env srfi55 "exec55" (Just "exec55_3")
      --libModules <- compileLisp env moduleFile "exec55_2" (Just "exec55_3")
      liftIO $ Language.Scheme.Core.registerExtensions env getDataFileName
      return (String "exec", libsC, libSrfi55C, []) --libModules)
    (_, _) -> return (String "run", [], [], [])

  -- Initialize the compiler module and begin
  _ <- initializeCompiler env
  execC <- compileLisp env filename nextFunc Nothing

  -- Append any additional import modules
  List imports <- getNamespacedVar env 't' {-"internal"-} "imports"
  let moreHeaderImports = map conv imports

  outH <- liftIO $ openFile outHaskell WriteMode
  _ <- liftIO $ writeList outH headerComment
  _ <- liftIO $ writeList outH headerModule
  _ <- liftIO $ writeList outH $ map (\mod -> "import " ++ mod ++ " ") $ headerImports ++ moreHeaderImports
  filepath <- liftIO $ getDataFileName ""
  _ <- liftIO $ writeList outH $ header filepath compileLibraries langrev
  _ <- liftIO $ case compileLibraries of
    True -> do
      _ <- writeList outH $ map show libsC
      _ <- hPutStrLn outH " ------ END OF STDLIB ------"
      _ <- writeList outH $ map show libSrfi55C
      hPutStrLn outH " ------ END OF SRFI 55 ------"
      -- _ <- writeList outH $ map show libModules
      hPutStrLn outH " ------ END OF MODULES ------"
    False -> do
      hPutStrLn outH "exec _ _ _ _ = return $ Nil \"\"" -- Placeholder
  _ <- liftIO $ writeList outH $ map show execC
  _ <- liftIO $ hClose outH
  if not (null execC)
     then return $ Nil "" -- Dummy value
     else throwError $ Default "Empty file" --putStrLn "empty file"

-- |Compile the intermediate haskell file using GHC
compileHaskellFile :: String -> String -> Bool -> String -> IO() --ThrowsError LispVal
compileHaskellFile hsInFile objOutFile dynamic extraArgs = do
  let ghc = "ghc" -- Need to make configurable??
      dynamicArg = if dynamic then "-dynamic" else ""
  compileStatus <- system $ ghc ++ " " ++ dynamicArg ++ " " ++ extraArgs ++ " -cpp --make -package ghc -o " ++ objOutFile ++ " " ++ hsInFile

-- TODO: delete intermediate hs files if requested

  case compileStatus of
    ExitFailure _code -> exitWith compileStatus
    ExitSuccess -> return ()

-- |Helper function to write a list of abstract Haskell code to file
writeList outH (l : ls) = do
  hPutStrLn outH l
  writeList outH ls
writeList outH _ = do
  hPutStr outH ""