packages feed

elm-repl-0.4: src/Eval/Code.hs

{-# LANGUAGE OverloadedStrings #-}
module Eval.Code (eval) where

import Control.Monad.Cont (ContT(ContT, runContT))
import Control.Monad.Error (ErrorT, runErrorT, throwError)
import Control.Monad.RWS (get, modify)
import Control.Monad.Trans (liftIO)
import qualified Data.Binary as Binary
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.List as List
import qualified Data.Map as Map
import System.Directory (doesFileExist, removeFile)
import System.FilePath ((</>), (<.>), replaceExtension)
import System.IO (hPutStrLn, stderr)

import qualified Environment as Env
import qualified Eval.Command as Eval
import qualified Input

import qualified Elm.Compiler.Module as Module
import qualified Elm.Compiler.Type as Type
import qualified Elm.Package.Description as Desc
import qualified Elm.Package.Name as Name
import qualified Elm.Package.Paths as Path
import qualified Elm.Package.Version as Version
import qualified Elm.Utils as Utils


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
        runCmd (Env.compilerPath env) (Env.flags env ++ elmArgs)
        liftIO $ addNodeRunner tempJsPath
        value <- runCmd (Env.interpreterPath env) [tempJsPath]
        liftIO $ printIfNeeded value
        liftIO $ removeIfExists tempElmPath
        liftIO $ removeIfExists tempJsPath
    return ()
  where
    runConts m = runContT m (\_ -> return ())
    
    tempElmPath =
        "repl-temp-000" <.> "elm"

    tempJsPath =
        replaceExtension tempElmPath "js"
    
    elmArgs =
        [ tempElmPath
        , "--yes"
        , "--output=" ++ tempJsPath
        ]

printIfNeeded :: String -> IO ()
printIfNeeded rawValue =
  case rawValue of
    "" -> return ()
    _ ->
      do  tipe <- getType
          let value = init rawValue

          let isTooLong =
                List.isInfixOf "\n" value
                  || List.isInfixOf "\n" tipe
                  || length value + 3 + length tipe > 80

          let tipeAnnotation =
                if isTooLong
                  then "\n    : " ++ List.intercalate "\n      " (lines tipe)
                  else " : " ++ tipe

          putStrLn (value ++ tipeAnnotation)


runCmd :: FilePath -> [String] -> ContT () IO String
runCmd name args = ContT $ \ret ->
  do  result <- liftIO (Utils.unwrappedRun name args)
      case result of
        Right stdout ->
            ret stdout

        Left (Utils.MissingExe msg) ->
            liftIO $ hPutStrLn stderr msg

        Left (Utils.CommandFailed out err) ->
            liftIO $ hPutStrLn stderr (out ++ err)


addNodeRunner :: String -> IO ()
addNodeRunner tempJsPath =
    BS.appendFile tempJsPath nodeRunner


nodeRunner :: BS.ByteString
nodeRunner =
    BS.pack $
    concat
    [ "process.on('uncaughtException', function(err) {\n\
      \  process.stderr.write(err.toString());\n\
      \  process.exit(1);\n\
      \});\n\
      \var document = document || {};\n\
      \var window = window || {};\n\
      \var context = { inputs:[], addListener:function(){}, node:{} };\n\
      \var repl = Elm.Repl.make(context);\n\
      \var toString = Elm.Native.Show.make(context).toString;\n"
    , "if ('", Env.lastVarString, "' in repl) {\n"
    , "  console.log(toString(repl.", Env.lastVarString, "));\n"
    , "}"
    ]


getType :: IO String
getType =
  do  result <- runErrorT getTypeHelp
      case result of
        Right tipe -> return tipe
        Left _ -> return ""


getTypeHelp :: ErrorT String IO String
getTypeHelp =
  do  description <- Desc.read Path.description
      binary <- liftIO (BS.readFile (interfacePath description))
      let types = Module.interfaceTypes (Binary.decode binary)
      case Map.lookup Env.lastVarString types of
        Just tipe -> return (Type.toString tipe)
        Nothing -> throwError "Type signature not found!"


interfacePath :: Desc.Description -> FilePath
interfacePath description =
    Path.stuffDirectory
        </> "build-artifacts"
        </> Name.toFilePath (Desc.name description)
        </> Version.toString (Desc.version description)
        </> "Repl.elmi"


removeIfExists :: FilePath -> IO ()
removeIfExists fileName =
    do  exists <- doesFileExist fileName
        if exists
          then removeFile fileName
          else return ()