
module Language.EWE.VM(runVM,execVM) where

import Language.EWE.AbsSyn
import Language.EWE.Utils(emptyProg, equates, stms)
import qualified Data.List as L
import qualified Data.Maybe as M
import qualified Data.Map.Strict as Map
import Control.Monad.Trans.State.Lazy
import Control.Monad.Trans.Class(lift)
import Data.Char (ord,chr)
import System.IO(hFlush, stdout,hPutStrLn,hPutStr)
import System.Exit(exitWith,ExitCode(..))

type Memory = Map.Map Int Int

emptyMemory :: Memory
emptyMemory = Map.empty

type PC = Int

initPC :: PC
initPC = 0

data StateVM = StVM { mem :: Memory
                    , prg :: Prog
                    , pc  :: PC
                    , debug :: Bool
                    , steps :: Int
                    } deriving (Show)

emptyStateVM = StVM { mem = emptyMemory
                    , prg = emptyProg
                    , pc  = initPC
                    , debug = False
                    , steps = 0
                    }

type StateVMM = StateT StateVM IO

runVM :: Prog -> IO ()
runVM prg = evalStateT evalVM (initVM prg)

execVM :: Prog -> IO StateVM
execVM prg = execStateT evalVM (initVM prg)

evalVM :: StateVMM ()
evalVM = do
  -- lift $ hPutStrLn stdout "Iniciando maquina"
  evalInstr

initVM :: Prog -> StateVM
initVM p = emptyStateVM { prg = p }

mRef :: MRef -> Equates -> Int
mRef (MRefI i) _  = i
mRef (MRefId s) m = M.fromMaybe (error $ "Internal error: memory reference " ++ s ++ " not found ") $ L.lookup s m

outMem :: Int -> Memory -> (Memory,Int)
outMem r m = M.maybe (Map.insert r 0 m, 0) ((,) m) (Map.lookup r m)

inMem :: Int -> Int -> Memory -> Memory
inMem = Map.insert

incrPC :: StateVM -> Int
incrPC state = (pc state) + 1

ge :: StateVM -> Equates
ge = equates.prg

evalInstr :: StateVMM ()
evalInstr = do
  st <- get
  let pc' = pc st
      prg' = prg st
      (Stmt _ ci)   = stms prg' !! pc'
      st'  = execInstr ci st
  if (hasSideEffects ci)
    then case (ci) of
         (IRI _)   -> do
             i <- lift $ readInt "Enter an integer: "
             let st'' = execIRI i ci st
             put st''
             evalInstr
         (IWI _)   -> do
             let (st'',i) = execIWI ci st
             lift $ putStrLn (show i)
             put st''
             evalInstr
         (IRS _ _) -> do
           s <- lift $ readStr "Enter a string: "
           let st'' = execIRS s ci st
           put st''
           evalInstr
         (IWS _) -> do
             let (st'',s) = execIWS ci st
             lift $ putStrLn (show s)
             put st''
             evalInstr
         (IH)      -> return ()
         (IB)      -> return ()
    else do put st'
            evalInstr

execInstr :: Instr -> StateVM -> StateVM
execInstr (INI)       state =
  state { pc = incrPC state }
execInstr (IMMI mr i) state =
  state { mem = inMem (mRef mr (ge state)) i (mem state)
        , pc  = incrPC state
        }
execInstr (IMMS mr s) st =
  let base  = mRef mr (ge st)
      total = base + length s
      st'   = moveStrInMem s base total st
  in st { mem = mem st'
        , pc = incrPC st
        }
execInstr (IMRPC mr i) state =
  state { mem = inMem (mRef mr (ge state)) ((pc state) + i) (mem state)
        , pc  = incrPC state
        }
execInstr (SPC mr) state =
  state { pc = (mRef mr (ge state))
        }
execInstr (IMMM mr1 mr2) state =
  let m       = mem state
      ge'     = ge state
      (m', v) = outMem (mRef mr2 ge') m
  in state { mem = inMem (mRef mr1 ge') v m'
           , pc  = incrPC state
           }
execInstr (IAdd mrr mra mrb) state = comp (+)   mrr mra mrb state
execInstr (ISub mrr mra mrb) state = comp (-)   mrr mra mrb state
execInstr (IMul mrr mra mrb) state = comp (*)   mrr mra mrb state
execInstr (IDiv mrr mra mrb) state = comp (div) mrr mra mrb state
execInstr (IMod mrr mra mrb) state = comp (mod) mrr mra mrb state
execInstr (IMRI mrr mr  i) state =
  let m          = mem state
      ge'        = ge state
      (m',v)     = outMem (mRef mr ge') m
      (m'', v')  = outMem (v + i) m'
  in state { mem = inMem (mRef mrr ge') v' m''
           , pc  = incrPC state
           }
execInstr (IMMR mri i mr) state =
  let m          = mem state
      ge'        = ge state
      (m', v)    = outMem (mRef mr ge') m
      (m'', v')  = outMem (mRef mri ge') m'
  in state { mem = inMem (v' + i) v m''
           , pc  = incrPC state
           }
execInstr (IGI i) state =
  let prg'  = prg state
      stms' = stms prg'
      l'    = length stms'
  in state { pc = if (i >= 0 && i < l')
                  then i else error "Inst Pos not valid"
           }
execInstr (IGS s) state =
  let prg'  = prg state
      stms' = stms prg'
      i     = lookupLabel s stms'
  in state { pc = i }
execInstr (IFI mra cond mrb i) state =
  let m       = mem state
      pc'     = pc state
      ge'     = ge state
      prg'    = prg state
      stms'   = stms prg'
      l'      = length stms'
      (m',a)  = outMem (mRef mra ge') m
      (m'',b) = outMem (mRef mrb ge') m'
      op      = fun cond
      i'      = if (i >= 0 && i < l')
                then i else error "Inst Pos not valid in condition"
      npc     = if a `op` b then i' else pc' + 1
  in state { mem = m''
           , pc  = npc
           }
execInstr (IFS mra cond mrb s) state =
  let m       = mem state
      pc'     = pc state
      ge'     = ge state
      prg'    = prg state
      stms'   = stms prg'
      l'      = length stms'
      (m',a)  = outMem (mRef mra ge') m
      (m'',b) = outMem (mRef mrb ge') m'
      op      = fun cond
      i       = lookupLabel s stms'
      npc     = if a `op` b then i else pc' + 1
  in state { mem = m''
           , pc  = npc
           }

execIRI :: Int -> Instr -> StateVM -> StateVM
execIRI i (IRI mr) state =
  state { mem = inMem (mRef mr (ge state)) i (mem state)
        , pc  = incrPC state
        }

execIWI :: Instr -> StateVM -> (StateVM, Int)
execIWI (IWI mr ) state =
  let m       = mem state
      ge'     = ge state
      (m', v) = outMem (mRef mr ge') m
  in (state { mem = m', pc  = incrPC state}, v)

execIRS :: String -> Instr -> StateVM -> StateVM
execIRS s (IRS mr1 mr2) state =
  let ge'     = ge state
      m       = mem state
      startP  = mRef mr1 ge'
      lenP    = mRef mr2 ge'
      (m', v) = outMem lenP m
      state'  = state { mem = m' }
      state'' = moveStrInMem s startP (startP + v) state'
  in if v >= 0
     then state'' { pc = incrPC state }
     else error "IRS len is negative"

moveStrInMem :: String -> Int -> Int -> StateVM -> StateVM
moveStrInMem [] st en state
  | st <= en   = moveStrInMem [] (st+1) en
                        (state { mem = inMem st 0 (mem state) })
  | otherwise  = state
moveStrInMem (c:cs) st en state
  | st <= en   = moveStrInMem cs (st+1) en
                        (state { mem = inMem st (ord c) (mem state) })
  | otherwise  = state { mem = inMem st 0 (mem state) }

execIWS :: Instr -> StateVM -> (StateVM, String)
execIWS (IWS mr) state =
  let ge'         = ge state
      start       = mRef mr ge'
      (state', s) = moveStrOutMem start [] state
  in (state' {pc = incrPC state }, s)

moveStrOutMem :: Int -> String -> StateVM -> (StateVM, String)
moveStrOutMem i s state =
  let ge'    = ge state
      m      = mem state
      (m',v) = outMem i m
  in if (v /= 0)
     then moveStrOutMem (i+1) (s++[chr(v)]) (state { mem = m'})
     else (state, s)

lookupLabel :: String -> Stmts -> Int
lookupLabel = lookupLabel' 0

lookupLabel' :: Int -> String -> Stmts -> Int
lookupLabel' n s [] = error "Label not found"
lookupLabel' n s ((Stmt []  _):stms) = lookupLabel' (n+1) s stms
lookupLabel' n s ((Stmt lbls _):stms)
    | s `elem` lbls = n
    | otherwise     = lookupLabel' (n+1) s stms

fun :: Cond -> (Int -> Int -> Bool)
fun CLET = (<=)
fun CLT  = (<)
fun CGET = (>=)
fun CGT  = (>)
fun CE   = (==)
fun CNE  = (/=)

-- comp computes the functions that transform int values
comp :: (Int -> Int -> Int) -> MRef -> MRef -> MRef -> StateVM -> StateVM
comp f mrr mra mrb state =
  let m          = mem state
      ge'        = ge state
      (m', a)    = outMem (mRef mra ge') m
      (m'', b)   = outMem (mRef mrb ge') m'
  in state { mem = inMem (mRef mrr ge') (a `f` b) m''
           , pc  = incrPC state
           }

hasSideEffects :: Instr -> Bool
hasSideEffects (IRI _)   = True
hasSideEffects (IWI _)   = True
hasSideEffects (IRS _ _) = True
hasSideEffects (IWS _)   = True
hasSideEffects (IH)      = True
hasSideEffects (IB)      = True
hasSideEffects _         = False

readInt :: String -> IO Int
readInt msg = do
  putStr (msg ++ "> ")
  hFlush stdout
  readLn

readStr :: String -> IO String
readStr msg = do
  putStr (msg ++ "> ")
  hFlush stdout
  s <- getLine
  -- putStrLn s
  return $ s

 lift $ hPutStrLn stdout "Iniciando maquina"
 comp computes the functions that transform int values
 putStrLn s

module Language.EWE.VM(runVM,execVM) where

import Language.EWE.AbsSyn
import Language.EWE.Utils(emptyProg, equates, stms)
import qualified Data.List as L
import qualified Data.Maybe as M
import qualified Data.Map.Strict as Map
import Control.Monad.Trans.State.Lazy
import Control.Monad.Trans.Class(lift)
import Data.Char (ord,chr)
import System.IO(hFlush, stdout,hPutStrLn,hPutStr)
import System.Exit(exitWith,ExitCode(..))

type Memory = Map.Map Int Int

emptyMemory :: Memory
emptyMemory = Map.empty

type PC = Int

initPC :: PC
initPC = 0

data StateVM = StVM { mem :: Memory
                    , prg :: Prog
                    , pc  :: PC
                    , debug :: Bool
                    , steps :: Int
                    } deriving (Show)

emptyStateVM = StVM { mem = emptyMemory
                    , prg = emptyProg
                    , pc  = initPC
                    , debug = False
                    , steps = 0
                    }

type StateVMM = StateT StateVM IO

runVM :: Prog -> IO ()
runVM prg = evalStateT evalVM (initVM prg)

execVM :: Prog -> IO StateVM
execVM prg = execStateT evalVM (initVM prg)

evalVM :: StateVMM ()
evalVM = do
  evalInstr

initVM :: Prog -> StateVM
initVM p = emptyStateVM { prg = p }

mRef :: MRef -> Equates -> Int
mRef (MRefI i) _  = i
mRef (MRefId s) m = M.fromMaybe (error $ "Internal error: memory reference " ++ s ++ " not found ") $ L.lookup s m

outMem :: Int -> Memory -> (Memory,Int)
outMem r m = M.maybe (Map.insert r 0 m, 0) ((,) m) (Map.lookup r m)

inMem :: Int -> Int -> Memory -> Memory
inMem = Map.insert

incrPC :: StateVM -> Int
incrPC state = (pc state) + 1

ge :: StateVM -> Equates
ge = equates.prg

evalInstr :: StateVMM ()
evalInstr = do
  st <- get
  let pc' = pc st
      prg' = prg st
      (Stmt _ ci)   = stms prg' !! pc'
      st'  = execInstr ci st
  if (hasSideEffects ci)
    then case (ci) of
         (IRI _)   -> do
             i <- lift $ readInt "Enter an integer: "
             let st'' = execIRI i ci st
             put st''
             evalInstr
         (IWI _)   -> do
             let (st'',i) = execIWI ci st
             lift $ putStrLn (show i)
             put st''
             evalInstr
         (IRS _ _) -> do
           s <- lift $ readStr "Enter a string: "
           let st'' = execIRS s ci st
           put st''
           evalInstr
         (IWS _) -> do
             let (st'',s) = execIWS ci st
             lift $ putStrLn (show s)
             put st''
             evalInstr
         (IH)      -> return ()
         (IB)      -> return ()
    else do put st'
            evalInstr

execInstr :: Instr -> StateVM -> StateVM
execInstr (INI)       state =
  state { pc = incrPC state }
execInstr (IMMI mr i) state =
  state { mem = inMem (mRef mr (ge state)) i (mem state)
        , pc  = incrPC state
        }
execInstr (IMMS mr s) st =
  let base  = mRef mr (ge st)
      total = base + length s
      st'   = moveStrInMem s base total st
  in st { mem = mem st'
        , pc = incrPC st
        }
execInstr (IMRPC mr i) state =
  state { mem = inMem (mRef mr (ge state)) ((pc state) + i) (mem state)
        , pc  = incrPC state
        }
execInstr (SPC mr) state =
  state { pc = (mRef mr (ge state))
        }
execInstr (IMMM mr1 mr2) state =
  let m       = mem state
      ge'     = ge state
      (m', v) = outMem (mRef mr2 ge') m
  in state { mem = inMem (mRef mr1 ge') v m'
           , pc  = incrPC state
           }
execInstr (IAdd mrr mra mrb) state = comp (+)   mrr mra mrb state
execInstr (ISub mrr mra mrb) state = comp (-)   mrr mra mrb state
execInstr (IMul mrr mra mrb) state = comp (*)   mrr mra mrb state
execInstr (IDiv mrr mra mrb) state = comp (div) mrr mra mrb state
execInstr (IMod mrr mra mrb) state = comp (mod) mrr mra mrb state
execInstr (IMRI mrr mr  i) state =
  let m          = mem state
      ge'        = ge state
      (m',v)     = outMem (mRef mr ge') m
      (m'', v')  = outMem (v + i) m'
  in state { mem = inMem (mRef mrr ge') v' m''
           , pc  = incrPC state
           }
execInstr (IMMR mri i mr) state =
  let m          = mem state
      ge'        = ge state
      (m', v)    = outMem (mRef mr ge') m
      (m'', v')  = outMem (mRef mri ge') m'
  in state { mem = inMem (v' + i) v m''
           , pc  = incrPC state
           }
execInstr (IGI i) state =
  let prg'  = prg state
      stms' = stms prg'
      l'    = length stms'
  in state { pc = if (i >= 0 && i < l')
                  then i else error "Inst Pos not valid"
           }
execInstr (IGS s) state =
  let prg'  = prg state
      stms' = stms prg'
      i     = lookupLabel s stms'
  in state { pc = i }
execInstr (IFI mra cond mrb i) state =
  let m       = mem state
      pc'     = pc state
      ge'     = ge state
      prg'    = prg state
      stms'   = stms prg'
      l'      = length stms'
      (m',a)  = outMem (mRef mra ge') m
      (m'',b) = outMem (mRef mrb ge') m'
      op      = fun cond
      i'      = if (i >= 0 && i < l')
                then i else error "Inst Pos not valid in condition"
      npc     = if a `op` b then i' else pc' + 1
  in state { mem = m''
           , pc  = npc
           }
execInstr (IFS mra cond mrb s) state =
  let m       = mem state
      pc'     = pc state
      ge'     = ge state
      prg'    = prg state
      stms'   = stms prg'
      l'      = length stms'
      (m',a)  = outMem (mRef mra ge') m
      (m'',b) = outMem (mRef mrb ge') m'
      op      = fun cond
      i       = lookupLabel s stms'
      npc     = if a `op` b then i else pc' + 1
  in state { mem = m''
           , pc  = npc
           }

execIRI :: Int -> Instr -> StateVM -> StateVM
execIRI i (IRI mr) state =
  state { mem = inMem (mRef mr (ge state)) i (mem state)
        , pc  = incrPC state
        }

execIWI :: Instr -> StateVM -> (StateVM, Int)
execIWI (IWI mr ) state =
  let m       = mem state
      ge'     = ge state
      (m', v) = outMem (mRef mr ge') m
  in (state { mem = m', pc  = incrPC state}, v)

execIRS :: String -> Instr -> StateVM -> StateVM
execIRS s (IRS mr1 mr2) state =
  let ge'     = ge state
      m       = mem state
      startP  = mRef mr1 ge'
      lenP    = mRef mr2 ge'
      (m', v) = outMem lenP m
      state'  = state { mem = m' }
      state'' = moveStrInMem s startP (startP + v) state'
  in if v >= 0
     then state'' { pc = incrPC state }
     else error "IRS len is negative"

moveStrInMem :: String -> Int -> Int -> StateVM -> StateVM
moveStrInMem [] st en state
  | st <= en   = moveStrInMem [] (st+1) en
                        (state { mem = inMem st 0 (mem state) })
  | otherwise  = state
moveStrInMem (c:cs) st en state
  | st <= en   = moveStrInMem cs (st+1) en
                        (state { mem = inMem st (ord c) (mem state) })
  | otherwise  = state { mem = inMem st 0 (mem state) }

execIWS :: Instr -> StateVM -> (StateVM, String)
execIWS (IWS mr) state =
  let ge'         = ge state
      start       = mRef mr ge'
      (state', s) = moveStrOutMem start [] state
  in (state' {pc = incrPC state }, s)

moveStrOutMem :: Int -> String -> StateVM -> (StateVM, String)
moveStrOutMem i s state =
  let ge'    = ge state
      m      = mem state
      (m',v) = outMem i m
  in if (v /= 0)
     then moveStrOutMem (i+1) (s++[chr(v)]) (state { mem = m'})
     else (state, s)

lookupLabel :: String -> Stmts -> Int
lookupLabel = lookupLabel' 0

lookupLabel' :: Int -> String -> Stmts -> Int
lookupLabel' n s [] = error "Label not found"
lookupLabel' n s ((Stmt []  _):stms) = lookupLabel' (n+1) s stms
lookupLabel' n s ((Stmt lbls _):stms)
    | s `elem` lbls = n
    | otherwise     = lookupLabel' (n+1) s stms

fun :: Cond -> (Int -> Int -> Bool)
fun CLET = (<=)
fun CLT  = (<)
fun CGET = (>=)
fun CGT  = (>)
fun CE   = (==)
fun CNE  = (/=)

comp :: (Int -> Int -> Int) -> MRef -> MRef -> MRef -> StateVM -> StateVM
comp f mrr mra mrb state =
  let m          = mem state
      ge'        = ge state
      (m', a)    = outMem (mRef mra ge') m
      (m'', b)   = outMem (mRef mrb ge') m'
  in state { mem = inMem (mRef mrr ge') (a `f` b) m''
           , pc  = incrPC state
           }

hasSideEffects :: Instr -> Bool
hasSideEffects (IRI _)   = True
hasSideEffects (IWI _)   = True
hasSideEffects (IRS _ _) = True
hasSideEffects (IWS _)   = True
hasSideEffects (IH)      = True
hasSideEffects (IB)      = True
hasSideEffects _         = False

readInt :: String -> IO Int
readInt msg = do
  putStr (msg ++ "> ")
  hFlush stdout
  readLn

readStr :: String -> IO String
readStr msg = do
  putStr (msg ++ "> ")
  hFlush stdout
  s <- getLine
  return $ s
