/versions/ioarray/Brainfuck.hs

http://github.com/rickardlindberg/brainfuck · Haskell · 117 lines · 90 code · 22 blank · 5 comment · 14 complexity · 519c22bdb441db148999ec390e96e4f0 MD5 · raw file

  1. module Brainfuck where
  2. import Data.Array.IO
  3. import Data.Char
  4. import Data.IORef
  5. import System.IO
  6. --
  7. data Command =
  8. MoveRight
  9. | MoveLeft
  10. | Increment
  11. | Decrement
  12. | Print
  13. | Read
  14. | LoopStart Int
  15. | LoopEnd Int
  16. | NOP
  17. deriving (Show, Eq)
  18. --
  19. data IOTape a = IOTape
  20. { ioTapePos :: IORef Int
  21. , ioTapeArray :: IOArray Int a
  22. }
  23. newIOTapeFromList :: [a] -> IO (IOTape a)
  24. newIOTapeFromList list = do
  25. pos <- newIORef 0
  26. arr <- newListArray (0, length list - 1) list
  27. return $ IOTape pos arr
  28. tapeMoveRight :: IOTape a -> IO ()
  29. tapeMoveRight tape = modifyIORef (ioTapePos tape) inc
  30. tapeMoveLeft :: IOTape a -> IO ()
  31. tapeMoveLeft tape = modifyIORef (ioTapePos tape) dec
  32. tapeMoveTo :: IOTape a -> Int -> IO ()
  33. tapeMoveTo tape n = writeIORef (ioTapePos tape) n
  34. tapeModify :: IOTape a -> (a -> a) -> IO ()
  35. tapeModify tape fn = do
  36. index <- readIORef (ioTapePos tape)
  37. value <- readArray (ioTapeArray tape) index
  38. writeArray (ioTapeArray tape) index (fn value)
  39. tapeCurrentValue :: IOTape a -> IO a
  40. tapeCurrentValue tape = do
  41. index <- readIORef (ioTapePos tape)
  42. value <- readArray (ioTapeArray tape) index
  43. return value
  44. --
  45. inc :: Int -> Int
  46. inc x = x + 1
  47. dec :: Int -> Int
  48. dec x = x - 1
  49. --
  50. type Token = (Int, Char)
  51. parse :: String -> [Command]
  52. parse str = parseTokens (tokenize str)
  53. tokenize :: String -> [Token]
  54. tokenize str = zip [0..length validCharacters - 1] validCharacters
  55. where
  56. validCharacters = filter (`elem` "<>+-.,[]") str
  57. parseTokens :: [Token] -> [Command]
  58. parseTokens [] = []
  59. parseTokens ((_, '<'):xs) = MoveLeft : parseTokens xs
  60. parseTokens ((_, '>'):xs) = MoveRight : parseTokens xs
  61. parseTokens ((_, '+'):xs) = Increment : parseTokens xs
  62. parseTokens ((_, '-'):xs) = Decrement : parseTokens xs
  63. parseTokens ((_, '.'):xs) = Print : parseTokens xs
  64. parseTokens ((_, ','):xs) = Read : parseTokens xs
  65. parseTokens ((n, '['):xs) = let (innerLoop, (n',_):rest) = extractInnerLoop xs
  66. in ((LoopStart n') : parseTokens innerLoop) ++ [LoopEnd n] ++ parseTokens rest
  67. extractInnerLoop :: [Token] -> ([Token], [Token])
  68. extractInnerLoop tokens = extractInnerLoop' 0 [] tokens
  69. where
  70. extractInnerLoop' n acc [] = error "unexpected end of input"
  71. extractInnerLoop' n acc ((j, x):xs)
  72. | x == '[' = extractInnerLoop' (n+1) (acc ++ [(j, x)]) xs
  73. | x == ']' && n == 0 = (acc, (j, x):xs)
  74. | x == ']' = extractInnerLoop' (n-1) (acc ++ [(j, x)]) xs
  75. | otherwise = extractInnerLoop' n (acc ++ [(j, x)]) xs
  76. --
  77. execute :: String -> IO ()
  78. execute str = do
  79. prog <- newIOTapeFromList $ parse str ++ [NOP]
  80. dat <- newIOTapeFromList [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  81. let loop = do
  82. let continue = tapeMoveRight prog >> loop
  83. let continueAt n = tapeMoveTo prog n >> loop
  84. command <- tapeCurrentValue prog
  85. case command of
  86. MoveRight -> tapeMoveRight dat >> continue
  87. MoveLeft -> tapeMoveLeft dat >> continue
  88. Increment -> tapeModify dat inc >> continue
  89. Decrement -> tapeModify dat dec >> continue
  90. Print -> tapeCurrentValue dat >>= (putChar . chr) >> hFlush stdout >> continue
  91. Read -> getChar >>= (\v -> tapeModify dat (const $ ord $ v)) >> continue
  92. LoopStart n -> tapeCurrentValue dat >>= \v -> if v == 0 then continueAt n else continue
  93. LoopEnd n -> tapeCurrentValue dat >>= \v -> if v == 0 then continue else continueAt n
  94. NOP -> putStrLn "done!"
  95. loop