/
Brainfuck.hs
71 lines (59 loc) · 2.32 KB
/
Brainfuck.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import Control.Monad.State
import Data.Char
import Data.Maybe
import Data.Word
import Text.ParserCombinators.Parsec ( Parser, parse, many, oneOf
, noneOf, between, char, (<|>))
-- zipper
data ListZipper a = ListZipper { getLeft :: [a]
, getValue :: a
, getRight :: [a]
} deriving Show
modifyValue :: (a -> a) -> ListZipper a -> ListZipper a
modifyValue f (ListZipper ls x rs) = ListZipper ls (f x) rs
forward :: ListZipper a -> ListZipper a
forward (ListZipper ls x (r:rs)) = ListZipper (x:ls) r rs
backward :: ListZipper a -> ListZipper a
backward (ListZipper (l:ls) x rs) = ListZipper ls l (x:rs)
-- brainfuck
data BFIns = Next | Prev | Inc | Dec | Read | Write | Loop [BFIns]
deriving Show
type BFCell = Word8
type BFState = ListZipper BFCell
type Brainfuck = StateT BFState IO
emptyState :: BFState
emptyState = ListZipper zeroes 0 zeroes
where zeroes = repeat 0
eval :: BFIns -> Brainfuck ()
eval Next = modify forward
eval Prev = modify backward
eval Inc = modify $ modifyValue (+1)
eval Dec = modify . modifyValue $ subtract 1
eval Write = gets getValue >>= liftIO . putStr . return . chr . fromEnum
eval Read = liftIO getChar >>= modify . modifyValue . const . toEnum . ord
eval loop@(Loop inside) = gets getValue >>= executeLoop
where executeLoop val | val == 0 = return ()
| otherwise = mapM_ eval $ inside ++ [loop]
runBF :: [BFIns] -> IO BFState
runBF = flip execStateT emptyState . mapM_ eval
-- parser
comment :: Parser (Maybe BFIns)
comment = noneOf "]" >> return Nothing
simpleIns :: Parser (Maybe BFIns)
simpleIns = oneOf "<>+-.," >>= \ins -> return . Just $ case ins of
'<' -> Next
'>' -> Prev
'+' -> Inc
'-' -> Dec
'.' -> Write
',' -> Read
loop :: Parser (Maybe BFIns)
loop = between (char '[') (char ']') (program >>= return . Just . Loop)
program :: Parser [BFIns]
program = liftM catMaybes $ many $ simpleIns <|> loop <|> comment
-- utility
parseAndRunBF :: String -> IO ()
parseAndRunBF str = do let parseRes = parse program "bf" str
case parseRes of
Left err -> putStrLn ("Syntax error: " ++ show err)
Right ins -> runBF ins >> putStrLn ""