ref: e2eb643f79f482babd16f8ad91c6a0074f4c845b
dir: /src/MicroHs/Interactive.hs/
module MicroHs.Interactive(module MicroHs.Interactive) where
import Data.List
import Data.Maybe
import Data.Version
import Control.Exception
import MicroHs.Compile
import MicroHs.CompileCache
import MicroHs.Desugar(LDef)
import MicroHs.Expr(EType, showEType)
import MicroHs.Flags
import MicroHs.Ident(mkIdent, Ident, unIdent, isIdentChar)
import MicroHs.List
import MicroHs.Parse
import MicroHs.StateIO
import MicroHs.SymTab(Entry(..), stEmpty, stKeysGlbU)
import MicroHs.Translate
import MicroHs.TypeCheck(ValueExport(..), TypeExport(..), TModule(..), Symbols)
import Unsafe.Coerce
import System.Console.SimpleReadline
import MicroHs.Instances(compiledWithGHC)
import Paths_MicroHs(version)
data IState = IState {
isLines :: String,
isFlags :: Flags,
isCache :: Cache,
isSymbols :: Symbols
}
type I a = StateIO IState a
mainInteractive :: Flags -> IO ()
mainInteractive flags = do
putStrLn $ "Welcome to interactive MicroHs, version " ++ showVersion version
let flags' = flags{ loading = True }
cash <- getCached flags'
_ <- runStateIO start $ IState preamble flags' cash noSymbols
return ()
noSymbols :: Symbols
noSymbols = (stEmpty, stEmpty)
preamble :: String
preamble = "module " ++ interactiveName ++ "(module " ++ interactiveName ++
") where\nimport Prelude\nimport System.IO.PrintOrRun\ndefault (Integer, Double, String, ())\n"
start :: I ()
start = do
reload
is <- get
liftIO $ maybeSaveCache (isFlags is) (isCache is)
liftIO $ putStrLn "Type ':quit' to quit, ':help' for help"
when compiledWithGHC $
liftIO $ putStrLn "WARNING: Compiled with GHC, so limited functionality."
repl
repl :: I ()
repl = do
syms <- gets isSymbols
ms <- liftIO $ getInputLineHistComp (return . complete syms) ".mhsi" "> "
case ms of
Nothing -> repl
Just s ->
case s of
[] -> repl
':':r -> do
c <- command r
if c then repl else liftIO $ putStrLn "Bye"
_ -> do
oneline s
repl
command :: String -> I Bool
command s =
case words s of
[] -> return True
c : ws ->
case filter (isPrefixOf c . fst) commands of
[] -> do
liftIO $ putStrLn "Unrecognized command"
return True
[(_, cmd)] ->
cmd (unwords ws)
xs -> do
liftIO $ putStrLn $ "Ambiguous command: " ++ unwords (map fst xs)
return True
commands :: [(String, String -> I Bool)]
commands =
[ ("quit", const $ return False)
, ("clear", const $ do
updateLines (const preamble)
modify $ \ is -> is{ isCache = emptyCache, isSymbols = noSymbols }
return True
)
, ("reload", const $ do
flgs <- gets isFlags
cash <- gets isCache
cash' <- liftIO $ validateCache flgs cash
modify $ \ is -> is{ isCache = cash' }
reload
return True
)
, ("delete", \ del -> do
updateLines (unlines . filter (not . isPrefixOf del) . lines)
return True
)
, ("type", \ line -> do
showType line
return True
)
, ("kind", \ line -> do
showKind line
return True
)
, ("help", \ _ -> do
liftIO $ putStrLn helpText
return True
)
]
reload :: I ()
reload = do
ls <- gets isLines
rld <- tryCompile ls -- reload modules right away
case rld of
Left msg -> liftIO $ err msg
Right _ -> return ()
helpText :: String
helpText = "\
\Commands:\n\
\:quit quit MicroHs\n\
\:reload reload modules\n\
\:clear clear all definitions\n\
\:delete d delete definition(s) d\n\
\:type e show type of e\n\
\:kind t show type of t\n\
\:help this text\n\
\expr evaluate expression\n\
\defn add top level definition\n\
\"
updateLines :: (String -> String) -> I ()
updateLines f = modify $ \ is -> is{ isLines = f (isLines is) }
updateCache :: (Cache -> Cache) -> I ()
updateCache f = modify $ \ is -> is{ isCache = f (isCache is) }
setSyms :: Symbols -> I ()
setSyms syms = modify $ \ is -> is{ isSymbols = syms }
interactiveName :: String
interactiveName = "Interactive"
interactiveId :: Ident
interactiveId = mkIdent interactiveName
itName :: String
itName = "_it"
itTypeName :: String
itTypeName = "Type_it"
itIOName :: String
itIOName = "_itIO"
mkIt :: String -> String
mkIt l =
itName ++ " = " ++ l ++ "\n"
mkItIO :: String -> String
mkItIO l =
mkIt l ++
itIOName ++ " = printOrRun " ++ itName ++ "\n"
mkTypeIt :: String -> String
mkTypeIt l =
"type " ++ itTypeName ++ " = " ++ l ++ "\n"
err :: SomeException -> IO ()
err e = err' $ displayException e
err' :: String -> IO ()
err' s = putStrLn $ "Exception: " ++ s
oneline :: String -> I ()
oneline line = do
ls <- gets isLines
let lls = ls ++ line ++ "\n"
def = do
defTest <- tryCompile lls
case defTest of
Right _ -> updateLines (const lls)
Left e -> liftIO $ err e
expr = do
exprTest <- tryCompile (ls ++ "\n" ++ mkItIO line)
case exprTest of
Right m -> evalExpr m
Left e -> liftIO $ err e
-- First try to parse as a definition,
tryParse pTopModule lls def $ \ _ ->
-- if that fails, parse as an expression.
tryParse pExprTop line expr $
liftIO . err'
tryParse :: forall a . Show a => P a -> String -> I () -> (String -> I ()) -> I ()
tryParse p s ok bad =
case parse p "" s of
Right _ -> ok
Left e -> bad e
tryCompile :: String -> I (Either SomeException [LDef])
tryCompile file = do
updateCache (deleteFromCache interactiveId)
flgs <- gets isFlags
cash <- gets isCache
liftIO $ writeFile (interactiveName ++ ".hs") file
res <- liftIO $ try $ compileCacheTop flgs interactiveId cash
case res of
Left e -> return (Left e)
Right ((_, m), syms, cash') -> do
updateCache (const cash')
setSyms syms
return (Right m)
evalExpr :: [LDef] -> I ()
evalExpr cmdl = do
let ares = translate (mkIdent (interactiveName ++ "." ++ itIOName), cmdl)
res = unsafeCoerce ares :: IO ()
mval <- liftIO $ try (seq res (return res))
liftIO $
case mval of
Left e -> err e
Right val -> do
mio <- try val
case mio of
Left e -> err e
Right _ -> return ()
showType :: String -> I ()
showType line = do
ls <- gets isLines
res <- tryCompile (ls ++ "\n" ++ mkIt line)
case res of
Right _ -> do
cash <- gets isCache
let t = getTypeInCache cash (mkIdent itName)
liftIO $ putStrLn $ showEType t
Left e ->
liftIO $ err e
showKind :: String -> I ()
showKind line = do
ls <- gets isLines
res <- tryCompile (ls ++ "\n" ++ mkTypeIt line)
case res of
Right _ -> do
cash <- gets isCache
let t = getKindInCache cash (mkIdent itTypeName)
liftIO $ putStrLn $ showEType t
Left e ->
liftIO $ err e
getCModule :: Cache -> TModule [LDef]
getCModule cash =
case lookupCache interactiveId cash of
Nothing -> undefined -- this cannot happen
Just cm -> cm
getTypeInCache :: Cache -> Ident -> EType
getTypeInCache cash i =
case getCModule cash of
TModule _ _ _ _ _ _ vals _ ->
head $ [ t | ValueExport i' (Entry _ t) <- vals, i == i' ] ++ [undefined]
getKindInCache :: Cache -> Ident -> EType
getKindInCache cash i =
case getCModule cash of
TModule _ _ tys _ _ _ _ _ ->
head $ [ k | TypeExport i' (Entry _ k) _ <- tys, i == i' ] ++ [undefined]
-- This could be smarter:
-- ":a" should complete with commands
-- "import A" should complete with modules
-- operator completion
-- completion with qualified names
complete :: Symbols -> (String, String) -> [String]
complete (tys, vals) (rpre, _post) =
let pre = reverse $ takeWhile isIdentChar rpre
allSyms = map unIdent $ stKeysGlbU tys ++ stKeysGlbU vals
allStrs = allSyms ++ keywords
real = notElem '$'
in case filter real $ catMaybes $ map (stripPrefix pre) allStrs of
[] -> []
[s] -> [s ++ " "]
ss ->
case findCommonPrefix ss of
[] -> ss
p -> [p]