144 lines
5.5 KiB
Haskell
144 lines
5.5 KiB
Haskell
module Interpreter where
|
|
import Data.Maybe (catMaybes)
|
|
import Debug.Trace
|
|
import Control.Monad.Trans.State
|
|
import qualified Data.Vector as V
|
|
|
|
data UpdType = Add | Sub deriving (Eq, Show)
|
|
data LoopType = Start | End deriving (Eq, Show)
|
|
data Instruction = TapeLeft | TapeRight | Update UpdType | Out | In |
|
|
Loop LoopType deriving (Eq, Show)
|
|
|
|
parseInstr :: Char -> Maybe Instruction
|
|
parseInstr '<' = Just TapeLeft
|
|
parseInstr '>' = Just TapeRight
|
|
parseInstr '+' = Just $ Update Add
|
|
parseInstr '-' = Just $ Update Sub
|
|
parseInstr '.' = Just Out
|
|
parseInstr ',' = Just In
|
|
parseInstr '[' = Just $ Loop Start
|
|
parseInstr ']' = Just $ Loop End
|
|
parseInstr _ = Nothing
|
|
|
|
newtype Byte = Byte Int deriving (Eq, Ord, Read, Show)
|
|
chr :: Byte -> Char
|
|
chr (Byte i) = toEnum i :: Char
|
|
|
|
updateCell :: UpdType -> Byte -> Byte
|
|
updateCell Add (Byte 255) = Byte 0
|
|
updateCell Sub (Byte 0) = Byte 255
|
|
updateCell Add (Byte i) = Byte (i + 1)
|
|
updateCell Sub (Byte i) = Byte (i - 1)
|
|
|
|
type Tape = V.Vector Byte
|
|
data Memory = Memory { tape :: Tape, dp :: Int } deriving (Show)
|
|
|
|
type Program = V.Vector Instruction
|
|
|
|
-- Consider putting IP in Termination output
|
|
data Term = Next Int | Crash Error Int | Done deriving (Show)
|
|
|
|
toProgram :: String -> Maybe Program
|
|
toProgram s = case progL of [] -> Nothing
|
|
_ -> Just $ V.fromList progL
|
|
where progL = catMaybes $ fmap parseInstr s
|
|
|
|
tapeHelp :: Tape -> Int -> (Byte -> Byte) -> Tape
|
|
tapeHelp t dp upd = t V.// [(dp, upd $ t V.! dp)]
|
|
|
|
data Error = NegTape | LoopNoMatch deriving (Show)
|
|
|
|
errorDesc :: Error -> String
|
|
errorDesc NegTape = "ERROR: '<' not working. Cannot access negative tape positions."
|
|
errorDesc LoopNoMatch = "ERROR: '[' or ']' not working. Matching parenthesis not found."
|
|
|
|
execUpdate :: UpdType -> State Memory (Maybe Error)
|
|
execUpdate i = state stateF
|
|
where stateF p = (Nothing, p { tape = newTape p })
|
|
newTape p = tapeHelp (tape p) (dp p) (updateCell i)
|
|
|
|
execTapeLeft :: State Memory (Maybe Error)
|
|
execTapeLeft = state $ \p -> case dp p of 0 -> (Just NegTape, p)
|
|
n -> (Nothing, p { dp = n - 1 })
|
|
|
|
execTapeRight :: State Memory (Maybe Error)
|
|
execTapeRight = state $ \p -> (Nothing, newMem p)
|
|
where newMem p = p { tape = newTape p, dp = dp p + 1 }
|
|
newTape p
|
|
| dp p == V.length (tape p) - 1 = V.snoc (tape p) $ Byte 0
|
|
| otherwise = tape p
|
|
|
|
execIn :: StateT Memory IO (Maybe Error)
|
|
execIn = StateT $ \p -> do chr <- getChar
|
|
tp <- pure $ fromEnum chr
|
|
return (Nothing, newTape tp p)
|
|
where newTape tp p = p { tape = tapeHelp (tape p) (dp p) (\_ -> Byte tp) }
|
|
|
|
execOut :: StateT Memory IO (Maybe Error)
|
|
execOut = StateT $ \p -> putChar (chr $ tape p V.! dp p) >>
|
|
(return (Nothing, p))
|
|
|
|
findMatching :: Program -> LoopType -> Int -> Maybe Int
|
|
findMatching prg par = fmHelper $ -1
|
|
where (oppos, limit, next) = case par of
|
|
Start -> (End, length prg, (+1))
|
|
End -> (Start, 0, (-1+))
|
|
|
|
fmHelper :: Int -> Int -> Maybe Int
|
|
fmHelper acc s
|
|
| s == limit = Nothing
|
|
| acc == 0 && c == Loop oppos = Just s
|
|
| c == Loop oppos = fmHelper (acc - 1) $ next s
|
|
| c == Loop par = fmHelper (acc + 1) $ next s
|
|
| otherwise = fmHelper acc $ next s
|
|
where c = prg V.! s
|
|
|
|
execLoop :: Program -> LoopType -> Int -> State Memory Term
|
|
execLoop p i ip = state $ \w -> case posOrF w of
|
|
Just n -> (Next $ n + 1, w)
|
|
Nothing -> (Crash LoopNoMatch ip, w)
|
|
where posOrF ps = case ((tape ps V.! dp ps) == Byte 0, i) of
|
|
(True, Start) -> findMatching p i ip
|
|
(False, End) -> findMatching p i ip
|
|
_ -> Just ip
|
|
|
|
execute :: Program -> Memory -> IO (Term, Memory)
|
|
execute p = runStateT $ runInstruction 0
|
|
where runInstruction :: Int -> StateT Memory IO Term
|
|
runInstruction ip = fmap checkDone (instrFor ip) >>= dbg >>= loopBack
|
|
where loopBack (Next i) = runInstruction i
|
|
loopBack x = return x
|
|
dbg a = StateT $ \s -> do _ <- putStrLn . show $ (a, s)
|
|
return (a, s)
|
|
|
|
instrFor :: Int -> StateT Memory IO Term
|
|
instrFor ip = case (p V.! ip) of
|
|
Update t -> toTerm ip . toStateT $ execUpdate t
|
|
Loop t -> toStateT $ execLoop p t ip
|
|
TapeLeft -> toTerm ip . toStateT $ execTapeLeft
|
|
TapeRight -> toTerm ip . toStateT $ execTapeRight
|
|
In -> toTerm ip execIn
|
|
Out -> toTerm ip execOut
|
|
|
|
toTerm ip = fmap termMem
|
|
where termMem Nothing = Next $ ip + 1
|
|
termMem (Just e) = Crash e ip
|
|
|
|
toStateT :: (Monad m) => State s a -> StateT s m a
|
|
toStateT x = StateT $ return . (runState x)
|
|
|
|
checkDone :: Term -> Term
|
|
checkDone (Next x)
|
|
| x == (V.length p) = Done
|
|
| otherwise = Next x
|
|
checkDone x = x
|
|
|
|
|
|
initMemory :: Memory
|
|
initMemory = Memory { tape = V.singleton $ Byte 0, dp = 0 }
|
|
|
|
run :: String -> IO ((Term, Memory))
|
|
run str = case toProgram str of Nothing -> return (Done, initMemory)
|
|
Just pp -> execute pp initMemory
|
|
-- vim: set ts=4 sw=4 et tw=80:
|