packages feed

cspmchecker-0.2.0: src/InteractiveChecker/Main.hs

module Main where

import Data.Char
import Control.Exception (AsyncException(..))
import Control.Monad.Trans
import Data.List
import Prelude hiding (catch)
import System.Console.Haskeline
import System.FilePath
import System.IO

import CSPM
import CSPM.Compiler.Processes
import Monad
import Util.Annotated
import Util.Exception
import Util.Monad
import Util.Prelude
import Util.PrettyPrint

main :: IO ()
main = do
    st <- initICheckerState
    -- Ensure the output is not buffered
    hSetBuffering stdout NoBuffering
    runIChecker st runICheckerInput

runICheckerInput :: IChecker ()
runICheckerInput = do
    settingsDir <- getState settingsDirectory
    let settings = setComplete iCheckerComplete $ defaultSettings {
            historyFile = Just $ 
                joinPath [settingsDir, "interactive", "prompt_history"]
        }
    runInputT settings interactiveLoop

interactiveLoop :: InputT IChecker ()
interactiveLoop = do
    currentPath <- lift $ getState currentFilePath
    let 
        prompt = case currentPath of
            Just fp -> last (splitPath fp)
            Nothing -> ""
    minput <- handleSourceError (Just "") (getInputLine (prompt ++ "> "))
    case minput of
        Nothing -> return ()
        Just input -> do
            c <- handleSourceError True (processInput input)
            if c then interactiveLoop else return ()

handleSourceError :: a -> InputT IChecker a -> InputT IChecker a
handleSourceError v prog = (prog `catch` handle v) `catch` handleInt v
    where
        handle :: a -> LibCSPMException -> InputT IChecker a
        handle v e = do
            printError (show e)
            return v
        handleInt :: a -> AsyncException -> InputT IChecker a 
        handleInt v UserInterrupt = do
            printError "Interrupted"
            return v

processInput :: String -> InputT IChecker Bool
processInput (':':str) = do
    let (cmd,rest) = break isSpace str
    case getCommand cmd of
        Just (_, f, _) -> f (dropWhile isSpace rest)
        Nothing -> do
            printError ("unknown command :"++ str)
            return True
processInput expr = 
    if dropWhile isSpace expr == "" then return True
    else keepGoing evaluate expr

type CommandFunc = String -> InputT IChecker Bool
type Command = (String, CommandFunc, CompletionFunc IChecker)

builtInCommands :: [Command]
builtInCommands = [
    ("load", keepGoing loadFileCommand, completeFilename),
    ("printProc", keepGoing printProcCommand, completeExpression),
    ("reload", keepGoing reload, noCompletion),
    ("type", keepGoing typeOfExpr, completeExpression),
    ("quit", quit, noCompletion)
    ]

getCommand :: String -> Maybe Command
getCommand cmd = 
    case getCommands cmd of
        c@(s,_,_):[] -> Just c
        _    -> Nothing

getCommands :: String -> [Command]
getCommands str = 
    [c | c@(n,_,_) <- builtInCommands, str `isPrefixOf` n]

-- Completers
lineBreakers :: [Char]
lineBreakers = " \t\n"
expressionBreakers :: [Char]
expressionBreakers = 
    lineBreakers 
    ++ "+/%*?!$.(),;[]{}\\|"

wrapCompleter :: [Char] -> (String -> IChecker [String]) -> CompletionFunc IChecker
wrapCompleter breakers fun = completeWord Nothing breakers
    $ fmap (map simpleCompletion) . fmap sort . fun

iCheckerComplete :: CompletionFunc IChecker
iCheckerComplete line@(left,_) =
    case firstWord of
        ':':cmd | null rest -> completeCommand line
                | otherwise -> (lookupCompleter cmd) line
        _                   -> completeExpression line
    where
        (firstWord,rest) = break isSpace $ dropWhile isSpace $ reverse left
        lookupCompleter cmd = 
            case getCommand cmd of
                Just (_,_,c) -> c
                Nothing -> noCompletion

completeCommand :: CompletionFunc IChecker
completeCommand = wrapCompleter lineBreakers $ 
    \str -> case str of
        ':':cmd -> return (map (\ (s,_,_) -> ':':s) (getCommands cmd))
        _       -> return []

completeExpression :: CompletionFunc IChecker
completeExpression = wrapCompleter expressionBreakers $ \str -> do
    ns <- getBoundNames
    return [s | n <- ns, let OccName s = nameOccurrence n, str `isPrefixOf` s]
    
-- Commands

keepGoing :: (String -> InputT IChecker a) -> String -> InputT IChecker Bool
keepGoing prog str = prog str >> return True

quit :: String -> InputT IChecker Bool
quit _ = return False

typeOfExpr :: String ->  InputT IChecker ()
typeOfExpr str = do
    pExpr <- parseExpression str
    rnExpr <- renameExpression pExpr
    typ <- typeOfExpression rnExpr
    outputStrLn $ show $ 
        text str <+> text "::" <+> prettyPrint typ
    return ()

loadFileCommand :: String -> InputT IChecker ()
loadFileCommand fname = do
    fname <- liftIO $ expandPathIO (trim fname)
    -- Reset the context
    lift resetCSPM
    lift $ modifyState (\st -> st { currentFilePath = Just fname })
    -- Handle the error here so that the filepath is remembered (for reloading)
    handleSourceError () $ do
        pFile <- parseFile fname
        rnFile <- renameFile pFile
        tcFile <- typeCheckFile rnFile
        dsFile <- desugarFile tcFile
        bindFile dsFile
        outputStrLn $ "Ok, loaded "++fname

reload :: String -> InputT IChecker ()
reload _ = do
    lift resetCSPM
    currentPath <- lift $ getState currentFilePath
    case currentPath of
        Just fp -> loadFileCommand fp
        Nothing -> return ()

evaluate :: String -> InputT IChecker ()
evaluate str = do
    pStmt <- parseInteractiveStmt str
    rnStmt <- renameInteractiveStmt pStmt
    tcStmt <- typeCheckInteractiveStmt rnStmt
    dsStmt <- desugarInteractiveStmt tcStmt
    case (unAnnotate dsStmt) of
        Bind d -> bindDeclaration d
        Evaluate e -> do
            v <- evaluateExpression e
            outputStrLn $ show $ prettyPrint v

printProcCommand :: String -> InputT IChecker ()
printProcCommand str = do
    pExpr <- parseExpression str
    rnExpr <- renameExpression pExpr
    tcExpr <- ensureExpressionIsOfType TProc rnExpr
    dsExpr <- desugarExpression tcExpr
    VProc p <- evaluateExpression dsExpr
    outputStrLn $ show $ prettyPrintAllRequiredProcesses p
    return ()