/versions/initial/Brainfuck.hs

http://github.com/rickardlindberg/brainfuck · Haskell · 137 lines · 101 code · 29 blank · 7 comment · 14 complexity · c7cb0a1150873cf54cacda47eef684c6 MD5 · raw file

  1. module Brainfuck where
  2. import Data.Char
  3. import Data.Maybe
  4. import qualified Data.Map as M
  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 Tape a = Tape
  20. { currentPos :: Int
  21. , values :: M.Map Int a
  22. , defaultValue :: a
  23. } deriving (Show, Eq)
  24. emptyTape :: a -> Tape a
  25. emptyTape def = Tape 0 M.empty def
  26. tapePut :: Int -> a -> Tape a -> Tape a
  27. tapePut pos value tape = tape { values = newValues }
  28. where
  29. newValues = M.insert pos value (values tape)
  30. tapeGet :: Tape a -> a
  31. tapeGet tape = M.findWithDefault (defaultValue tape) (currentPos tape) (values tape)
  32. tapeModifyValue :: Tape a -> (a -> a) -> Tape a
  33. tapeModifyValue tape fn = tape { values = newValues }
  34. where
  35. value = M.findWithDefault (defaultValue tape) (currentPos tape) (values tape)
  36. newValues = M.insert (currentPos tape) (fn value) (values tape)
  37. tapeMoveRight :: Tape a -> Tape a
  38. tapeMoveRight = tapeModifyPos inc
  39. tapeMoveLeft :: Tape a -> Tape a
  40. tapeMoveLeft = tapeModifyPos dec
  41. tapeMoveTo :: Int -> Tape a -> Tape a
  42. tapeMoveTo pos = tapeModifyPos (const pos)
  43. tapeModifyPos :: (Int -> Int) -> Tape a -> Tape a
  44. tapeModifyPos fn tape = tape { currentPos = fn (currentPos tape) }
  45. --
  46. inc :: Int -> Int
  47. inc x = x + 1
  48. dec :: Int -> Int
  49. dec x = x - 1
  50. --
  51. type Program = Tape Command
  52. type Data = Tape Int
  53. --
  54. type Token = (Int, Char)
  55. parse :: String -> Program
  56. parse str = parseTokens (tokenize str) (emptyTape NOP)
  57. tokenize :: String -> [Token]
  58. tokenize str = zip [0..length f - 1] f
  59. where
  60. f = filter (`elem`"<>+-.,[]") str
  61. parseTokens :: [Token] -> Program -> Program
  62. parseTokens [] p = p
  63. parseTokens ((n, '>'):xs) p = parseTokens xs $ tapePut n MoveRight p
  64. parseTokens ((n, '<'):xs) p = parseTokens xs $ tapePut n MoveLeft p
  65. parseTokens ((n, '+'):xs) p = parseTokens xs $ tapePut n Increment p
  66. parseTokens ((n, '-'):xs) p = parseTokens xs $ tapePut n Decrement p
  67. parseTokens ((n, '.'):xs) p = parseTokens xs $ tapePut n Print p
  68. parseTokens ((n, ','):xs) p = parseTokens xs $ tapePut n Read p
  69. parseTokens ((n, '['):xs) p = let (innerLoop, (n',_):rest) = extractInnerLoop xs
  70. in parseTokens rest
  71. $ tapePut n' (LoopEnd n)
  72. $ parseTokens innerLoop
  73. $ tapePut n (LoopStart n') p
  74. parseTokens ((n, x):xs) p = error $ "unknown symbol: " ++ [x]
  75. extractInnerLoop :: [Token] -> ([Token], [Token])
  76. extractInnerLoop tokens = extractInnerLoop' 0 [] tokens
  77. where
  78. extractInnerLoop' n acc [] = error "unexpected end of input"
  79. extractInnerLoop' n acc ((j, x):xs)
  80. | x == '[' = extractInnerLoop' (n+1) (acc ++ [(j, x)]) xs
  81. | x == ']' && n == 0 = (acc, (j, x):xs)
  82. | x == ']' = extractInnerLoop' (n-1) (acc ++ [(j, x)]) xs
  83. | otherwise = extractInnerLoop' n (acc ++ [(j, x)]) xs
  84. --
  85. run :: Program -> Data -> IO ()
  86. run p d =
  87. case tapeGet p of
  88. MoveRight -> run (tapeMoveRight p) (tapeMoveRight d)
  89. MoveLeft -> run (tapeMoveRight p) (tapeMoveLeft d)
  90. Increment -> run (tapeMoveRight p) (tapeModifyValue d inc)
  91. Decrement -> run (tapeMoveRight p) (tapeModifyValue d dec)
  92. Print -> do
  93. putChar $ chr $ tapeGet d
  94. hFlush stdout
  95. run (tapeMoveRight p) d
  96. Read -> do
  97. c <- getChar
  98. run (tapeMoveRight p) (tapeModifyValue d (const $ ord c))
  99. LoopStart n -> if tapeGet d == 0
  100. then run (tapeMoveTo n p) d
  101. else run (tapeMoveRight p) d
  102. LoopEnd n -> if tapeGet d == 0
  103. then run (tapeMoveRight p) d
  104. else run (tapeMoveTo n p) d
  105. NOP -> putStrLn "done!"
  106. --
  107. execute :: String -> IO ()
  108. execute program = run (parse program) (emptyTape 0)