module Main where
import Data.Version
import Options.Applicative hiding (ParseError)
import System.Console.Shell
import System.Console.Shell.ShellMonad
import System.Console.Shell.Backend.Readline (readlineBackend)
import qualified Paths_lambda_calculator as P
import Language.Lambda
import Language.Lambda.Util.PrettyPrint
import Language.SystemF
main :: IO ()
main = execParser opts >>= runShell'
where opts = info (helper <*> cliParser)
(briefDesc <> progDesc "A Lambda Calculus Interpreter")
-- Option Parsing
data CliOptions = CliOptions {
language :: Eval Language,
version :: Bool
}
-- Supported Languages:
--
-- * Untyped Lambda Calculus
-- * System F
data Language
= Untyped
| SystemF
-- The result of an evaluation
type Result a = Either a -- An error
a -- The result
-- Represent a language together with its evaluation function
data Eval a = Eval a (String -> Result String)
untyped :: Eval Language
untyped = Eval Untyped eval
where eval = fromEvalString Language.Lambda.evalString
systemf :: Eval Language
systemf = Eval SystemF eval
where eval = fromEvalString Language.SystemF.evalString
-- Take a typed evaluation function and return a function that returns a result
--
-- For example:
-- (String -> Either ParseError (LambdaExpr String)) -> (String -> Result String)
-- (String -> Either ParseError (SystemFExpr String String)) -> (String -> Result String)
fromEvalString :: (Show s, PrettyPrint p)
=> (String -> Either s p)
-> (String -> Result String)
fromEvalString f = either (Left . show) (Right . prettyPrint) . f
cliParser :: Parser CliOptions
cliParser = CliOptions
<$> flag untyped systemf (long "system-f" <>
short 'f' <>
internal <> -- this is a secret feature
help "Use the System F interpreter")
<*> switch (long "version" <>
short 'v' <>
help "Print the version")
-- Interactive Shell
runShell' :: CliOptions -> IO ()
runShell' CliOptions{version=True} = putStrLn version'
runShell' CliOptions{language=Eval lang eval}
= runShell (mkShellDesc lang eval) readlineBackend ()
mkShellDesc :: Language
-> (String -> Result String)
-> ShellDescription ()
mkShellDesc language f = shellDesc' $ mkShellDescription commands (eval f)
where shellDesc' d = d {
greetingText = Just shellGreeting,
prompt = shellPrompt language
}
shellGreeting :: String
shellGreeting = "Lambda Calculator (" ++ version' ++ ")\nType :h for help\n"
shellPrompt :: Language -> s -> IO String
shellPrompt language _ = return $ prefix language : " > "
where prefix Untyped = lambda
prefix SystemF = upperLambda
commands :: [ShellCommand s]
commands = [
exitCommand "q",
helpCommand "h"
]
eval :: (String -> Result String) -> String -> Sh s' ()
eval f = either shellPutErrLn shellPutStrLn . f
-- Get the current version
version' :: String
version' = showVersion P.version