elm-repl-0.3: src/Eval/Code.hs
{-# LANGUAGE OverloadedStrings #-}
module Eval.Code (eval) where
import Control.Monad.Cont (ContT(ContT, runContT))
import Control.Monad.RWS (get, modify)
import Control.Monad.Trans (liftIO)
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString as BS
import qualified Data.Char as Char
import qualified Elm.Internal.Paths as Elm
import System.Directory (doesFileExist, removeFile)
import System.Exit (ExitCode(ExitFailure, ExitSuccess))
import System.FilePath ((</>), (<.>), replaceExtension)
import System.IO (hPutStrLn, stderr, stdout)
import System.Process (readProcessWithExitCode)
import qualified Environment as Env
import qualified Eval.Command as Eval
import qualified Input
eval :: (Maybe Input.DefName, String) -> Eval.Command ()
eval code =
do modify $ Env.insert code
env <- get
liftIO $ writeFile tempElmPath (Env.toElmCode env)
liftIO . runConts $ do
types <- runCmd (Env.compilerPath env) (Env.flags env ++ elmArgs)
liftIO $ reformatJS tempJsPath
value <- runCmd (Env.interpreterPath env) [tempJsPath]
liftIO $ printIfNeeded value (scrapeOutputType types)
liftIO $ removeIfExists tempElmPath
return ()
where
runConts m = runContT m (\_ -> return ())
tempElmPath =
"repl-temp-000" <.> "elm"
tempJsPath =
"build" </> replaceExtension tempElmPath "js"
elmArgs =
[ "--make"
, "--only-js"
, "--print-types"
, tempElmPath
]
printIfNeeded :: BS.ByteString -> BS.ByteString -> IO ()
printIfNeeded rawValue tipe =
if BSC.null rawValue
then return ()
else BSC.hPutStrLn stdout message
where
value = BSC.init rawValue
isTooLong =
BSC.isInfixOf "\n" value
|| BSC.isInfixOf "\n" tipe
|| BSC.length value + BSC.length tipe > 80
message =
BS.concat
[ if isTooLong then rawValue else value
, tipe
]
runCmd :: FilePath -> [String] -> ContT () IO BS.ByteString
runCmd name args = ContT $ \ret ->
do (exitCode, stdout, stderr) <-
liftIO $ readProcessWithExitCode name args ""
case exitCode of
ExitSuccess -> ret (BSC.pack stdout)
ExitFailure code
| code == 127 -> failure missingExe -- UNIX
| code == 9009 -> failure missingExe -- Windows
| otherwise -> failure (stdout ++ stderr)
where
failure message = liftIO $ hPutStrLn stderr message
missingExe =
unlines $
[ "Error: '" ++ name ++ "' command not found."
, " Do you have it installed?"
, " Can it be run from anywhere? I.e. is it on your PATH?" ]
reformatJS :: String -> IO ()
reformatJS tempJsPath =
do rts <- BS.readFile Elm.runtime
src <- BS.readFile tempJsPath
BS.length src `seq` BS.writeFile tempJsPath (BS.concat [rts,src,out])
where
out =
BS.concat
[ "process.on('uncaughtException', function(err) {\n"
, " process.stderr.write(err.toString());\n"
, " process.exit(1);\n"
, "});\n"
, "var document = document || {};"
, "var window = window || {};"
, "var context = { inputs:[], addListener:function(){}, node:{} };\n"
, "var repl = Elm.Repl.make(context);\n"
, "var show = Elm.Native.Show.make(context).show;"
, "if ('", Env.lastVar, "' in repl)\n"
, " console.log(show(repl.", Env.lastVar, "));"
]
scrapeOutputType :: BS.ByteString -> BS.ByteString
scrapeOutputType rawTypeDump =
dropName (squashSpace relevantLines)
where
squashSpace :: [BS.ByteString] -> BS.ByteString
squashSpace multiLineTypeDecl =
BSC.unwords (BSC.words (BSC.unwords multiLineTypeDecl))
dropName :: BS.ByteString -> BS.ByteString
dropName typeDecl =
BSC.cons ' ' (BSC.dropWhile (/= ':') typeDecl)
relevantLines :: [BS.ByteString]
relevantLines =
takeType . dropWhile (not . isLastVar) $ BSC.lines rawTypeDump
isLastVar :: BS.ByteString -> Bool
isLastVar line =
BS.isPrefixOf Env.lastVar line
|| BS.isPrefixOf (BS.append "Repl." Env.lastVar) line
takeType :: [BS.ByteString] -> [BS.ByteString]
takeType lines =
case lines of
[] -> error errorMessage
line : rest ->
line : takeWhile isMoreType rest
isMoreType :: BS.ByteString -> Bool
isMoreType line =
not (BS.null line)
&& Char.isSpace (BSC.head line)
errorMessage =
"Internal error in elm-repl function scrapeOutputType\n\
\Please report this bug to <https://github.com/elm-lang/elm-repl/issues>"
removeIfExists :: FilePath -> IO ()
removeIfExists fileName =
do exists <- doesFileExist fileName
if exists
then removeFile fileName
else return ()