{-# LANGUAGE BangPatterns #-}
module SMR.CLI.Repl where
import SMR.Core.Exp
import qualified SMR.CLI.Help                   as Help
import qualified SMR.CLI.Driver.Load            as Driver
import qualified SMR.Core.Step                  as Step
import qualified SMR.Core.World                 as World
import qualified SMR.Prim.Name                  as Prim
import qualified SMR.Prim.Op                    as Prim
import qualified SMR.Prim.Op.Base               as Prim
import qualified SMR.Source.Parser              as Source
import qualified SMR.Source.Lexer               as Source
import qualified SMR.Source.Pretty              as Source
import qualified SMR.Source.Expected            as Source
import qualified Data.Text.Lazy.IO              as TL
import qualified Data.Text.Lazy.Builder         as BL
import qualified System.Console.Haskeline       as HL
import qualified Data.Char                      as Char
import qualified Data.Map                       as Map
import qualified Data.Set                       as Set
import qualified Data.Text                      as Text
import Control.Monad.IO.Class
import Data.Text                                (Text)
import Data.Set                                 (Set)
import Data.Monoid


-------------------------------------------------------------------------------
data Mode s p w
        = ModeNone
        | ModeParse
        | ModePush (Exp s p)
        | ModeStep (Step.Config s p w) (Exp s p)


data State s p w
        = State
        { -- | Current interpreter mode.
          stateMode     :: Mode s p w

          -- | Top-level declarations parsed from source files.
        , stateDecls    :: [Decl s p]

          -- | Working source files.
        , stateFiles    :: [FilePath]

          -- | Execution world.
        , stateWorld    :: World.World w }


type RState     = State Text Prim.Prim ()
type RConfig    = Step.Config Text Prim.Prim ()
type RWorld     = World.World  ()
type RDecl      = Decl  Text Prim.Prim
type RExp       = Exp   Text Prim.Prim


-------------------------------------------------------------------------------
replStart :: RState -> IO ()
replStart state
 = HL.runInputT HL.defaultSettings
 $ do   HL.outputStrLn "Shimmer, version 0.1. The Lambda Machine."
        HL.outputStrLn "Type :help for help."
        replReload state


-- | Main repl loop dispatcher
replLoop :: RState -> HL.InputT IO ()
replLoop state
 = do   minput  <- HL.getInputLine "> "
        case minput of
         Nothing
          -> return ()

         Just input
          |  all Char.isSpace input
          -> case stateMode state of
                ModeNone        -> replLoop state
                ModePush xx     -> replPush_next state xx
                ModeStep c xx   -> replStep_next state c xx
                _               -> replLoop state

          | otherwise
          -> case words input of
                ":quit"    : []   -> replQuit    state
                ":help"    : []   -> replHelp    state
                ":reload"  : []   -> replReload  state
                ":r"       : []   -> replReload  state
                ":grammar" : []   -> replGrammar state
                ":prims"   : []   -> replPrims   state

                ":decls"   : xs
                 -> let strip ('@' : name) = name
                        strip name         = name
                    in  replDecls state
                                $ Set.fromList $ map Text.pack
                                $ map strip xs

                ":parse"   : xs   -> replParse   state (unwords xs)
                ":push"    : xs   -> replPush    state (unwords xs)
                ":step"    : xs   -> replStep    state (unwords xs)
                ":steps"   : xs   -> replSteps   state (unwords xs)
                ":trace"   : xs   -> replTrace   state (unwords xs)
                _                 -> replSteps   state input


-------------------------------------------------------------------------------
-- | Quit the repl.
replQuit  :: RState -> HL.InputT IO ()
replQuit _state
 = do   return ()


-------------------------------------------------------------------------------
-- | Display the help page.
replHelp  :: RState -> HL.InputT IO ()
replHelp state
 = do   HL.outputStr $ Help.helpCommands
        replLoop state


-------------------------------------------------------------------------------
-- | Display the language grammar.
replGrammar  :: RState -> HL.InputT IO ()
replGrammar state
 = do   HL.outputStr $ Help.helpGrammar
        replLoop state


-------------------------------------------------------------------------------
-- | Display the list of primops.
replPrims  :: RState -> HL.InputT IO ()
replPrims state
 = do   HL.outputStrLn
         $ "  name          params    description"

        HL.outputStrLn
         $ "  ----          ------    -----------"

        HL.outputStr
         $ unlines
         [ "  #unit                   unit value"
         , "  #true                   boolean true"
         , "  #false                  boolean false"
         , "  #nat'NAT                natural number"
         , "  #list                   list constructor" ]

        HL.outputStr
         $ unlines
         $ [   leftPad 16 ("  #" ++ (Text.unpack $ name))
            ++ leftPad 10  (concat [showForm f | f <- Prim.primEvalForm p])
            ++ Text.unpack (Prim.primEvalDesc p)

           | p@(Prim.PrimEval { Prim.primEvalName = Prim.PrimOp name })
                <- Prim.primOps ]

        replLoop state

showForm :: Form -> String
showForm PVal   = "!"
showForm PExp   = "~"

leftPad :: Int -> [Char] -> [Char]
leftPad n ss
 = ss ++ replicate (n - length ss) ' '


-------------------------------------------------------------------------------
-- | Display the list of current declarations.
replDecls :: RState -> Set Name -> HL.InputT IO ()
replDecls state names
 = do   liftIO  $ mapM_ (printDecl names)
                $ stateDecls state

        replLoop state


printDecl :: Set Name -> RDecl -> IO ()
printDecl names decl
 | Set.null names
 = do TL.putStr
         $ BL.toLazyText
         $ Source.buildDecl decl

 | DeclMac name _ <- decl
 , Set.member name names
 = do   TL.putStr
         $ BL.toLazyText
         $ Source.buildDecl decl

 | otherwise
 = return ()


-------------------------------------------------------------------------------
-- | Reload the current source file.
replReload :: RState -> HL.InputT IO ()
replReload state
 = do
        decls   <- liftIO
                $  fmap concat $ mapM Driver.runLoadFileDecls
                $  stateFiles state

        replLoop (state
                { stateDecls    = decls })


-------------------------------------------------------------------------------
-- | Parse and print back an expression.
replParse :: RState -> String -> HL.InputT IO ()
replParse state str
 = do   result  <- liftIO $ replParseExp state str
        case result of
         Nothing
          -> replLoop state

         Just xx
          -> do liftIO  $ TL.putStrLn
                        $ BL.toLazyText
                        $ Source.buildExp Source.CtxTop xx
                HL.outputStr "\n"

                replLoop state


-------------------------------------------------------------------------------
-- | Parse an expression and push down substitutions.
replPush :: RState -> String -> HL.InputT IO ()
replPush state str
 = do   result  <- liftIO $ replParseExp state str
        case result of
         Nothing -> replLoop state
         Just xx -> replPush_next state xx


-- | Advance the train pusher.
replPush_next :: RState -> RExp -> HL.InputT IO ()
replPush_next state xx
 = case pushDeep xx of
        Nothing -> replLoop $ state { stateMode = ModeNone }
        Just xx'
         -> do  liftIO  $ TL.putStrLn
                        $ BL.toLazyText
                        $ Source.buildExp Source.CtxTop xx'

                replLoop $ state { stateMode = ModePush xx' }


-------------------------------------------------------------------------------
-- | Parse an expression and single-step it.
replStep :: RState -> String -> HL.InputT IO ()
replStep state str
 = replLoadExp state str replStep_next

-- | Advance the single stepper.
replStep_next
        :: RState -> RConfig -> RExp
        -> HL.InputT IO ()

replStep_next state config xx
 = do   erx     <- liftIO $ Step.step config (stateWorld state) xx
        case erx of
         Left Step.ResultDone
          -> replLoop $ state { stateMode = ModeNone }

         Left (Step.ResultError msg)
          -> do  HL.outputStrLn
                         $ Text.unpack
                         $ Text.pack "error: " <> msg

         Right xx'
          -> do  liftIO  $ TL.putStrLn
                         $ BL.toLazyText
                         $ Source.buildExp Source.CtxTop xx'

                 replLoop $ state { stateMode = ModeStep config xx' }


-------------------------------------------------------------------------------
-- | Parse an expression and normalize it.
replSteps :: RState -> String -> HL.InputT IO ()
replSteps state str
 = replLoadExp state str replSteps_next

-- | Advance the evaluator stepper.
replSteps_next
        :: RState -> RConfig -> RExp
        -> HL.InputT IO ()

replSteps_next state config xx
 = do   erx     <- liftIO $ Step.steps config (stateWorld state) xx
        case erx of
         Left msg
          -> do  HL.outputStrLn
                         $ Text.unpack
                         $ Text.pack "error: " <> msg

         Right xx'
          -> do  liftIO  $ TL.putStrLn
                         $ BL.toLazyText
                         $ Source.buildExp Source.CtxTop xx'

                 replLoop $ state { stateMode = ModeNone }


-------------------------------------------------------------------------------
-- | Parse an expression and normalize it,
--   printing out each intermediate state.
replTrace :: RState -> String -> HL.InputT IO ()
replTrace state str
 = replLoadExp state str replTrace_next

-- | Advance the evaluator stepper.
replTrace_next
        :: RState -> RConfig -> RExp
        -> HL.InputT IO ()

replTrace_next state config !xx0
 = loop xx0
 where
  loop !xx
   = do erx <- liftIO $ Step.step config (stateWorld state) xx
        case erx of
         Left (Step.ResultError msg)
          -> do  HL.outputStrLn
                  $ Text.unpack
                  $ Text.pack "error: " <> msg

         Left Step.ResultDone
          -> replLoop $ state { stateMode = ModeNone }

         Right xx'
          -> do  liftIO  $ TL.putStrLn
                         $ BL.toLazyText
                         $ Source.buildExp Source.CtxTop xx'

                 loop xx'

-------------------------------------------------------------------------------
replLoadExp
        :: RState -> String
        -> (RState -> RConfig -> RExp -> HL.InputT IO ())
        -> HL.InputT IO ()
replLoadExp state str eat
 = do   result  <- liftIO $ replParseExp state str
        case result of
         Nothing -> replLoop state

         Just xx
          -> let
                decls   = Map.fromList
                        $ [ (n, x) | DeclMac n x <- stateDecls state ]

                prims   = Map.fromList
                        $ [ (Prim.primEvalName p, p) | p <- Prim.primOps ]

                config  = Step.Config
                        { Step.configUnderLambdas = True
                        , Step.configHeadArgs     = True
                        , Step.configDeclsMac     = decls
                        , Step.configPrims        = prims }

              in eat state config xx


-------------------------------------------------------------------------------
replParseExp :: RState -> String -> IO (Maybe RExp)
replParseExp _state str
 = do   let (ts, _loc, _csRest)
                = Source.lexTokens (Source.L 1 1) str

        let config
                = Source.Config
                { Source.configReadSym  = Just
                , Source.configReadPrm  = Prim.readPrim Prim.primNames }

        case Source.parseExp config ts of
         Left err
          -> do liftIO  $ putStrLn
                        $ "parse error\n"
                        ++ Source.pprParseError err
                return Nothing

         Right xx
          -> return (Just xx)