/Brainfuck.hs

https://github.com/DasIch/Brainfuck · Haskell · 102 lines · 85 code · 17 blank · 0 comment · 28 complexity · 9b5f2644d5391982c845b6fd72516f39 MD5 · raw file

  1. module Main where
  2. import System.Environment (getArgs)
  3. import Control.Monad (liftM, foldM)
  4. import Text.ParserCombinators.Parsec hiding (parse)
  5. import qualified Text.ParserCombinators.Parsec as P (parse)
  6. import Data.List.Zipper (Zipper, fromList, right, left, cursor, replace)
  7. import Data.Char (ord, chr)
  8. import Data.Word (Word8)
  9. main :: IO ()
  10. main = do
  11. args <- getArgs
  12. let path = parseArgs args
  13. code <- readFile path
  14. let ast = parse code
  15. let memory = emptyMemory 1024
  16. interpret memory ast
  17. return ()
  18. parseArgs :: [String] -> String
  19. parseArgs (x:[]) = x
  20. parseArgs xs = error "Provide one argument, the path to the program"
  21. parse :: String -> AST
  22. parse code = case P.parse program "Brainfuck" (clean code) of
  23. Left err -> error $ show err
  24. Right ast -> optimize ast
  25. clean :: String -> String
  26. clean [] = []
  27. clean (x:xs) = if x `elem` "+-<>[].,"
  28. then x:clean xs
  29. else clean xs
  30. type AST = [Instruction]
  31. data Instruction = Add Int -- Add positive/negative value
  32. | Move Int -- Move pointer
  33. | Loop [Instruction]
  34. | Input -- Read one character
  35. | Output -- Write one character
  36. deriving (Show, Eq)
  37. program :: Parser AST
  38. program = many instruction
  39. instruction :: Parser Instruction
  40. instruction = loop <|> operator
  41. loop :: Parser Instruction
  42. loop = liftM Loop $ between (char '[') (char ']') program
  43. operator :: Parser Instruction
  44. operator = liftM (Add . length) (several '+')
  45. <|> liftM (Add . negate . length) (several '-')
  46. <|> liftM (Move . length) (several '>')
  47. <|> liftM (Move . negate . length) (several '<')
  48. <|> (char ',' >> return Input)
  49. <|> (char '.' >> return Output)
  50. where several c = many1 $ char c
  51. optimize :: AST -> AST
  52. optimize [] = []
  53. optimize (Loop []:xs) = optimize xs
  54. optimize (Loop xs:ys) = let optimizedContent = optimize xs
  55. in if optimizedContent == []
  56. then optimize ys
  57. else Loop optimizedContent:optimize ys
  58. optimize (Add m:Add n:xs) = if abs m == abs n && m /= n
  59. then Add (abs m):optimize xs
  60. else Add m:Add n:optimize xs
  61. optimize (Move m:Move n:xs) = if abs m == abs n && m /= n
  62. then Move (abs m):optimize xs
  63. else Move m:Move n:optimize xs
  64. optimize (x:xs) = x:optimize xs
  65. type Memory = Zipper Word8
  66. emptyMemory :: Int -> Memory
  67. emptyMemory size = fromList $ replicate size 0
  68. interpret :: Memory -> AST -> IO Memory
  69. interpret = foldM step
  70. step :: Memory -> Instruction -> IO Memory
  71. step mem op = case op of
  72. Add n -> return $ replace (fromIntegral n + cursor mem) mem
  73. Move n -> return $ shift n mem
  74. Loop instructions -> performLoop mem instructions
  75. Output -> putChar (chr . fromEnum . cursor $ mem) >> return mem
  76. Input -> fmap (flip replace mem . fromIntegral . ord) getChar
  77. where performLoop mem ins = if cursor mem == 0
  78. then return mem
  79. else interpret mem ins >>= flip performLoop ins
  80. shift :: Int -> Zipper a -> Zipper a
  81. shift 0 zipper = zipper
  82. shift 1 zipper = right zipper
  83. shift (-1) zipper = left zipper
  84. shift n zipper = if n > 0 then shift (n - 1) (shift 1 zipper)
  85. else shift (n + 1) (shift (-1) zipper)