husk-scheme-3.12: hs-src/Interpreter/shell.hs
{- |
Module : Main
Copyright : Justin Ethier
Licence : MIT (see LICENSE in the distribution)
Maintainer : github.com/justinethier
Stability : experimental
Portability : portable
This file implements a REPL "shell" to host the interpreter, and also
allows execution of stand-alone files containing Scheme code.
-}
module Main where
import Paths_husk_scheme
import qualified Language.Scheme.Core as LSC -- Scheme Interpreter
import Language.Scheme.Types -- Scheme data types
import qualified Language.Scheme.Util as LSU (strip)
import qualified Language.Scheme.Variables as LSV -- Scheme variable operations
import Control.Monad.Error
import qualified Data.Char as DC
import qualified Data.List as DL
import System.Cmd (system)
import System.Console.GetOpt
import qualified System.Console.Haskeline as HL
import qualified System.Console.Haskeline.Completion as HLC
import System.Environment
import System.Exit (ExitCode (..), exitWith, exitFailure)
import System.IO
main :: IO ()
main = do
args <- getArgs
let (actions, nonOpts, msgs) = getOpt Permute options args
opts <- foldl (>>=) (return defaultOptions) actions
let Options {optSchemeRev = schemeRev} = opts
if null nonOpts
then do
LSC.showBanner
runRepl schemeRev
else runOne schemeRev nonOpts
--
-- Command line options section
--
data Options = Options {
optSchemeRev :: String -- RxRS version
}
-- |Default values for the command line options
defaultOptions :: Options
defaultOptions = Options {
optSchemeRev = "5"
}
options :: [OptDescr (Options -> IO Options)]
options = [
Option ['r'] ["revision"] (ReqArg writeRxRSVersion "Scheme") "scheme RxRS version",
Option ['h', '?'] ["help"] (NoArg showHelp) "show usage information"
]
writeRxRSVersion arg opt = return opt { optSchemeRev = arg }
showHelp :: Options -> IO Options
showHelp _ = do
putStrLn "Usage: huski [options] [file]"
putStrLn ""
putStrLn " huski is the husk scheme interpreter."
putStrLn ""
putStrLn " File is a scheme source file to execute. If no file is specified"
putStrLn " the husk REPL will be started."
putStrLn ""
putStrLn " Options may be any of the following:"
putStrLn ""
putStrLn " -h -? --help Display this information"
-- TODO: specify scheme rev via command line
-- putStrLn " -r --revision Specify the scheme revision to use:"
-- putStrLn ""
-- putStrLn " 5 - r5rs (default)"
-- putStrLn " 7 - r7rs small"
putStrLn ""
exitWith ExitSuccess
--
-- REPL Section
--
flushStr :: String -> IO ()
flushStr str = putStr str >> hFlush stdout
-- |Execute a single scheme file from the command line
runOne :: String -> [String] -> IO ()
runOne _ args = do
env <- LSC.r5rsEnv >>= flip LSV.extendEnv
[((LSV.varNamespace, "args"),
List $ map String $ drop 1 args)]
result <- (LSC.runIOThrows $ liftM show $
LSC.evalLisp env (List [Atom "load", String (args !! 0)]))
case result of
Just errMsg -> putStrLn errMsg
_ -> do
-- Call into (main) if it exists...
alreadyDefined <- liftIO $ LSV.isBound env "main"
let argv = List $ map String $ args
when alreadyDefined (do
mainResult <- (LSC.runIOThrows $ liftM show $
LSC.evalLisp env (List [Atom "main", List [Atom "quote", argv]]))
case mainResult of
Just errMsg -> putStrLn errMsg
_ -> return ())
-- |Start the REPL (interactive interpreter)
runRepl :: String -> IO ()
runRepl _ = do
env <- LSC.r5rsEnv
let settings = HL.Settings (completeScheme env) Nothing True
HL.runInputT settings (loop env)
where
loop :: Env -> HL.InputT IO ()
loop env = do
minput <- HL.getInputLine "huski> "
case minput of
Nothing -> return ()
Just i -> do
case LSU.strip i of
"quit" -> return ()
"" -> loop env -- ignore inputs of just whitespace
input -> do
result <- liftIO (LSC.evalString env input)
if (length result) > 0
then do HL.outputStrLn result
loop env
else loop env
-- |Auto-complete using scheme symbols
completeScheme env (lnL, lnR) = do
complete $ reverse $ readAtom lnL
where
complete ('"' : _) = do
-- Special case, inside a string it seems more
-- useful to autocomplete filenames
liftIO $ HLC.completeFilename (lnL, lnR)
complete pre = do
-- Get list of possible completions from ENV
xps <- LSV.recExportsFromEnv env
let allDefs = xps ++ specialForms
let allDefs' = filter (\ (Atom a) -> DL.isPrefixOf pre a) allDefs
let comps = map (\ (Atom a) -> HL.Completion a a False) allDefs'
-- Get unused portion of the left-hand string
let unusedLnL = case DL.stripPrefix (reverse pre) lnL of
Just s -> s
Nothing -> lnL
return (unusedLnL, comps)
-- Not loaded into an env, so we need to list them here
specialForms = map (\ s -> Atom s) [
"define"
, "define-syntax"
, "expand"
, "hash-table-delete!"
, "hash-table-set!"
, "if"
, "lambda"
, "let-syntax"
, "letrec-syntax"
, "quote"
, "set!"
, "set-car!"
, "set-cdr!"
, "string-set!"
, "vector-set!"]
-- Read until the end of the current symbol (atom), if there is one.
-- There is also a special case for files if a double-quote is found.
readAtom (c:cs)
| c == '"' = ['"'] -- Save to indicate file completion to caller
| c == '(' = []
| c == '[' = []
| DC.isSpace(c) = []
| otherwise = (c : readAtom cs)
readAtom [] = []