egison-4.2.0: hs-src/Interpreter/egison.hs
{-# LANGUAGE LambdaCase #-}
module Main where
import Control.Exception (AsyncException (..))
import Control.Monad (when)
import Control.Monad.Catch (catch)
import Control.Monad.Reader
import Data.List (intercalate)
import qualified Data.Text as T
import Data.Version
import System.Console.Haskeline (InputT, Settings (..), getHistory, getInputLine, putHistory, runInputT)
import System.Console.Haskeline.History (addHistoryUnlessConsecutiveDupe)
import System.Directory (getHomeDirectory)
import System.Exit (exitFailure, exitSuccess)
import System.FilePath ((</>))
import System.IO
import Text.Regex.TDFA ((=~))
import Language.Egison
import Language.Egison.Completion
import Options.Applicative
main :: IO ()
main = execParser cmdParser >>= runWithOptions
isInValidMathOption :: EgisonOpts -> Bool
isInValidMathOption EgisonOpts{ optMathExpr = Just lang } = lang `notElem` ["asciimath", "latex", "mathematica", "maxima", "haskell"]
isInValidMathOption EgisonOpts{ optMathExpr = Nothing } = False
runWithOptions :: EgisonOpts -> IO ()
runWithOptions opts | isInValidMathOption opts =
hPrint stderr (Default "this output lang is not supported") >> exitFailure
runWithOptions EgisonOpts{ optShowVersion = True } =
putStrLn (showVersion version) >> exitSuccess
runWithOptions opts = evalRuntimeT opts run
run :: RuntimeM ()
run = do
opts <- ask
coreEnv <- initialEnv
mEnv <- fromEvalT $ evalTopExprs coreEnv $ map Load (optLoadLibs opts) ++ map LoadFile (optLoadFiles opts)
case mEnv of
Left err -> liftIO $ print err
Right env -> handleOption env opts
handleOption :: Env -> EgisonOpts -> RuntimeM ()
handleOption env opts =
case opts of
-- Evaluate the given string
EgisonOpts { optEvalString = Just expr } ->
runAndPrintExpr env expr
-- Execute the given string
EgisonOpts { optExecuteString = Just cmd } ->
executeTopExpr env $ "execute (" ++ cmd ++ ")"
-- Operate input in tsv format as infinite stream
EgisonOpts { optSubstituteString = Just sub } ->
let (sopts, copts) = unzip (optFieldInfo opts)
sopts' = "[" ++ intercalate ", " sopts ++ "]"
copts' = "[" ++ intercalate ", " copts ++ "]"
expr = "load \"lib/core/shell.egi\"\n"
++ "execute (let SH.input := SH.genInput " ++ sopts' ++ " " ++ copts' ++ "\n"
++ if optTsvOutput opts then " in each (\\x -> print (showTsv x)) ((" ++ sub ++ ") SH.input))"
else " in each (\\x -> print (show x)) ((" ++ sub ++ ") SH.input))"
in executeTopExpr env expr
-- Execute a script (test only)
EgisonOpts { optTestOnly = True, optExecFile = Just (file, _) } -> do
exprs <- liftIO $ readFile file
result <- if optNoIO opts
then fromEvalT (runTopExprs env exprs)
else fromEvalT (evalTopExprs env [LoadFile file])
liftIO $ either print (const $ return ()) result
-- Execute a script from the main function
EgisonOpts { optExecFile = Just (file, args) } -> do
result <- fromEvalT $ evalTopExprs env [LoadFile file, Execute (makeApply "main" [CollectionExpr (map (ConstantExpr . StringExpr . T.pack) args)])]
liftIO $ either print (const $ return ()) result
EgisonOpts { optMapTsvInput = Just expr } ->
handleOption env (opts { optSubstituteString = Just $ "\\x -> map (" ++ expr ++ ") x" })
EgisonOpts { optFilterTsvInput = Just expr } ->
handleOption env (opts { optSubstituteString = Just $ "\\x -> filter (" ++ expr ++ ") x" })
-- Start the read-eval-print-loop
_ -> do
when (optShowBanner opts) (liftIO showBanner)
repl env
when (optShowBanner opts) (liftIO showByebyeMessage)
liftIO exitSuccess
runAndPrintExpr :: Env -> String -> RuntimeM ()
runAndPrintExpr env expr = do
isTsvOutput <- asks optTsvOutput
if isTsvOutput
then executeTopExpr env $ "execute (each (\\x -> print (showTsv x)) (" ++ expr ++ "))"
else executeTopExpr env $ "execute (print (show (" ++ expr ++ ")))"
executeTopExpr :: Env -> String -> RuntimeM ()
executeTopExpr env expr = do
cmdRet <- fromEvalT (runTopExprs env expr)
case cmdRet of
Left err -> liftIO $ hPrint stderr err >> exitFailure
_ -> liftIO exitSuccess
showBanner :: IO ()
showBanner = do
putStrLn $ "Egison Version " ++ showVersion version
putStrLn "https://www.egison.org"
putStrLn "Welcome to Egison Interpreter!"
-- putStrLn $ "** Information **"
-- putStrLn $ "We can use the tab key to complete keywords on the interpreter."
-- putStrLn $ "If we press the tab key after a closed parenthesis, the next closed parenthesis will be completed."
-- putStrLn $ "*****************"
showByebyeMessage :: IO ()
showByebyeMessage = putStrLn "Leaving Egison Interpreter."
settings :: MonadIO m => FilePath -> Env -> Settings m
settings home env =
Settings { complete = completeEgison env
, historyFile = Just (home </> ".egison_history")
, autoAddHistory = False
}
repl :: Env -> RuntimeM ()
repl env = (do
home <- liftIO getHomeDirectory
input <- runInputT (settings home env) getEgisonExpr
case input of
Nothing -> return ()
Just topExpr -> do
result <- fromEvalT (evalTopExprStr env topExpr)
case result of
Left err -> liftIO (print err) >> repl env
Right (Just str, env') -> liftIO (putStrLn str) >> repl env'
Right (Nothing, env') -> repl env'
)
`catch`
(\case
UserInterrupt -> liftIO (putStrLn "") >> repl env
StackOverflow -> liftIO (putStrLn "Stack over flow!") >> repl env
HeapOverflow -> liftIO (putStrLn "Heap over flow!") >> repl env
_ -> liftIO (putStrLn "error!") >> repl env
)
-- |Get Egison expression from the prompt. We can handle multiline input.
getEgisonExpr :: InputT RuntimeM (Maybe TopExpr)
getEgisonExpr = getEgisonExpr' ""
where
getEgisonExpr' prev = do
opts <- lift ask
mLine <- case prev of
"" -> getInputLine $ optPrompt opts
_ -> getInputLine $ replicate (length $ optPrompt opts) ' '
case mLine of
Nothing -> return Nothing
Just [] | null prev -> getEgisonExpr
Just [] -> getEgisonExpr' prev
Just line -> do
history <- getHistory
putHistory $ addHistoryUnlessConsecutiveDupe line history
let input = prev ++ line
parsedExpr <- lift $ parseTopExpr (replaceNewLine input)
case parsedExpr of
Left err | err =~ "unexpected end of input" ->
getEgisonExpr' (input ++ "\n")
Left err -> do
liftIO $ putStrLn ("Parse error at: " ++ err)
getEgisonExpr
Right topExpr -> do
-- outputStr $ show topExpr
return $ Just topExpr
replaceNewLine :: String -> String
replaceNewLine input =
let (before, _, after) = input =~ "#newline" :: (String, String, String) in
case after of
"" -> before
_ -> before ++ "\n" ++ replaceNewLine after