I am very new to Haskell and come from languages like C++, although I do have some experience with Scheme. Here, I wrote a simple brainf*** interpreter, which so far is my largest project. I followed a tutorial for some of the code (such as the Tape
data type), but did most of this on my own. I have one major concern with my approach, which is the way I handle the loops (which in brainf*** are written with []
and repeat until the current cell in the tape is zero. In my implementation, I used the loop as a parameter to the main recursive function, which is called if the current instruction is ]
and the current cell is not 0
. Since these loops modify the tape, I had to make them return an instance of Tape
that would replace the current one. However, since the function also handles IO (and therefore must return an instance of IO
), I made it return an IO (Tape Int)
which is then unpacked in a do
block. This all felt very messy and hacky, so I would really appreciate any help from more experienced Haskell developers.
import Data.Maybe
--The Tape data type and functions
data Tape a = Tape [a] a [a]
newTape :: a -> Tape a
newTape x = Tape r x r
where r = repeat x
moveLeft :: Tape a -> Tape a
moveLeft (Tape (l:ls) x rs) = Tape ls l (x:rs)
moveRight :: Tape a -> Tape a
moveRight (Tape ls x (r:rs)) = Tape (x:ls) r rs
--The Brainf*** instruction data types
data BfInstruction
= MovLeft
| MovRight
| Increment
| Decrement
| Output
| Input
| BeginLoop
| EndLoop
deriving (Show, Eq)
type BfProgram = [BfInstruction]
--Convert string to BfProgram
parseBf :: String -> BfProgram
parseBf = mapMaybe parse
where
parse :: Char -> Maybe BfInstruction
parse x = case x of
'<' -> Just MovLeft
'>' -> Just MovRight
'+' -> Just Increment
'-' -> Just Decrement
',' -> Just Input
'.' -> Just Output
'[' -> Just BeginLoop
']' -> Just EndLoop
x -> Nothing --anything but the above chars is a comment
--Main running function
runBf :: String -> IO ()
runBf p = runBf' (parseBf p) (newTape 0) [] >> return ()
where
runBf' :: BfProgram -> Tape Int -> BfProgram -> IO (Tape Int)
runBf' [] tape _ = return tape
runBf' prog@(p:ps) tape@(Tape ls x rs) loop = case p of
MovLeft -> advance prog (moveLeft tape)
MovRight -> advance prog (moveRight tape)
Increment -> advance prog (Tape ls (x+1) rs)
Decrement -> advance prog (Tape ls (x-1) rs)
Input -> do
char <- getChar
advance prog (Tape ls (fromEnum char) rs)
Output -> putChar (toEnum x) >> advance prog tape
BeginLoop ->
let lp = getLoop 1 ps
in runBf' (length lp `drop` ps) tape lp --Drop so that we are at the ] now
EndLoop ->
if x /= 0
then do
lt <- runBf' loop tape []
runBf' prog lt loop --Copy the tape from the result of the loop into next iteration
else advance prog tape
advance :: BfProgram -> Tape Int -> IO (Tape Int)
advance (p:ps) tape = runBf' ps tape []
getLoop :: Int -> BfProgram -> BfProgram
getLoop _ [] = error "Mismatched brackets in BF program"
getLoop 1 (EndLoop:ps) = []
getLoop n (p:ps) = p:case p of
BeginLoop -> getLoop (n + 1) ps
EndLoop -> getLoop (n - 1) ps
_ -> getLoop n ps
--Simple IO
main = do
program <- readFile "program.bf"
runBf program