{-# LANGUAGE OverloadedStrings #-} module Evaluator where import qualified Data.Char as Char import qualified Data.ByteString.Char8 as BSC import qualified Data.ByteString as BS import qualified Elm.Internal.Paths as Elm import qualified Environment as Env import qualified Data.Map as Map import Control.Applicative ((<$>), (<*>)) import Control.Exception import Control.Monad (unless) import System.Directory (removeFile) import System.Exit (ExitCode(..)) import System.FilePath ((), replaceExtension) import System.IO import System.IO.Error (isDoesNotExistError) import System.Process runRepl :: String -> Env.Repl -> IO Env.Repl runRepl "" env = return env runRepl input oldEnv = do writeFile tempElm $ Env.toElm newEnv success <- runCmdWithCallback (Env.compilerPath newEnv) elmArgs $ \types -> do reformatJS input tempJS runCmdWithCallback "node" nodeArgs $ \value' -> let value = BSC.init value' tipe = scrapeOutputType types isTooLong = BSC.isInfixOf "\n" value || BSC.isInfixOf "\n" tipe || BSC.length value + BSC.length tipe > 80 message = BS.concat [ if isTooLong then value' else value, tipe ] in do unless (BSC.null value') $ BSC.hPutStrLn stdout message return True removeIfExists tempElm return $ if success then newEnv else oldEnv where newEnv = Env.insert input oldEnv tempElm = "repl-temp-000.elm" tempJS = "build" replaceExtension tempElm "js" nodeArgs = [tempJS] elmArgs = Env.flags newEnv ++ ["--make", "--only-js", "--print-types", tempElm] runCmdWithCallback :: FilePath -> [String] -> (BS.ByteString -> IO Bool) -> IO Bool runCmdWithCallback name args callback = do (_, stdout, stderr, handle') <- createProcess (proc name args) { std_out = CreatePipe , std_err = CreatePipe} exitCode <- waitForProcess handle' case (exitCode, stdout, stderr) of (ExitSuccess, Just out, Just _) -> callback =<< BS.hGetContents out (ExitFailure 127, Just _, Just _) -> failure missingExe (ExitFailure _, Just out, Just err) -> do e <- BSC.hGetContents err o <- BSC.hGetContents out failure (BS.concat [o,e]) (_, _, _) -> failure "Unknown error!" where failure message = BSC.hPutStrLn stderr message >> return False missingExe = BSC.pack $ 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 -> String -> IO () reformatJS input tempJS = do rts <- BS.readFile Elm.runtime src <- BS.readFile tempJS BS.length src `seq` BS.writeFile tempJS (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" , "if ('", Env.output, "' in repl)\n" , " console.log(context.Native.Show.values.show(repl.", Env.output, "));" ] scrapeOutputType :: BS.ByteString -> BS.ByteString scrapeOutputType = dropName . squashSpace . takeType . dropWhile (not . isOut) . BSC.lines where isOut = BS.isPrefixOf Env.output dropName = BS.drop $ BSC.length Env.output takeType (n:rest) = n : takeWhile isMoreType rest isMoreType = (&&) <$> not . BS.null <*> (Char.isSpace . BSC.head) squashSpace = BSC.unwords . BSC.words . BSC.unwords freshLine :: BS.ByteString -> (BS.ByteString, BS.ByteString) freshLine str | BS.null rest' = (line,"") | otherwise = (line, BS.tail rest') where (line,rest') = BSC.break (=='\n') str removeIfExists :: FilePath -> IO () removeIfExists fileName = removeFile fileName `Control.Exception.catch` handleExists where handleExists e | isDoesNotExistError e = return () | otherwise = throwIO e