packages feed

yabi-0.1.0.0: yabi.hs

import Data.Word8 (Word8)
import Data.Char (ord, chr)
import System.Environment (getArgs)
import System.IO (readFile, putChar, getChar, stdout, stdin, hSetBuffering, BufferMode (NoBuffering))
import System.Exit (exitFailure)

--the memory & program zippers
type Memory = ([Word8],[Word8]) -- the memory zipper: (left, pointer:right)
type Program = ([Char],[Char]) -- the instructions zipper: (todo,done)

--the default memory, an infinite zipper filled with zeros
defaultMemory :: ([Word8],[Word8])
defaultMemory = ((repeat 0),(repeat 0))

--breaks a string when the brackets are balanced
-- an open bracket adds 1 to the balance, a closed one subtracts 1
breakWhenBalanced :: Int -> String -> (String, String)
breakWhenBalanced 0 str = ("",str)
breakWhenBalanced balance (s:str) | s == '[' = let (first, second) = breakWhenBalanced (balance+1) str in (s:first, second)
                                  | s == ']' = let (first, second) = breakWhenBalanced (balance-1) str in (s:first, second)
                                  | otherwise = let (first, second) = breakWhenBalanced balance str in (s:first, second)
breakWhenBalanced balance [] = error $ "malformed code, brackets balance off by " ++ (show balance)

--the actual interpreter
bf :: Program -> Memory -> IO ()
bf ([], _) _ = return () --end of program
--move the pointer
bf ('>':commands, done) (ml, m:mr) = bf (commands, '>':done) (m:ml, mr)
bf ('<':commands, done) (m:ml, mr) = bf (commands, '<':done) (ml, m:mr)
--change the pointed byte
bf ('+':commands, done) (ml, m:mr) = bf (commands, '+':done) (ml, (m+1):mr)
bf ('-':commands, done) (ml, m:mr) = bf (commands, '-':done) (ml, (m-1):mr)
--output of pointed byte
bf ('.':commands, done) (ml, m:mr) = do
                                       putChar $ chr $ fromIntegral m
                                       bf (commands, '.':done) (ml, m:mr)
--input to pointed byte
bf (',':commands, done) (ml, _:mr) = do
                                       char <- getChar
                                       let m = fromIntegral $ ord char
                                       bf (commands, ',':done) (ml, m:mr)
--if pointed byte == 0 then jump forward to the corresponding ]
bf ('[':commands, done) (ml, m:mr) | m == 0 = bf (commands', done') (ml, m:mr)
                                   | otherwise = bf (commands, '[':done) (ml, m:mr)
    where (token, commands') = breakWhenBalanced 1 commands
          done' = (reverse token) ++ '[':done
--if pointed byte /= 0 then jump back to the corresponding [
bf (']':commands, done) (ml, m:mr) | m /= 0 = bf (commands', done') (ml, m:mr)
                                   | otherwise = bf (commands, ']':done) (ml, m:mr) --the interpreter works even without this line and "| m /= 0" on the line above, but this way is faster
    where (token, done') = breakWhenBalanced (-1) done
          commands' = (reverse token) ++ ']':commands

--debug
--memory dump ('#', according to Urban Müller's original interpreter)
bf ('#':commands, done) (ml,m:mr) = do
                                   putStrLn "Memory dump:"
                                   putStrLn $ "  " ++ (show $ takeWhile (/=0) ml) ++ " >" ++ (show m) ++ "< " ++ (show $ takeWhile (/=0) mr)
                                   bf (commands, '#':done) (ml,m:mr)
--program dump (i picked a random not-widely-used character for this)
bf ('§':commands, done) (ml,mr) = do
                                   putStrLn "Program dump:"
                                   putStrLn $ "  " ++ (reverse done) ++ "|" ++ commands
                                   putStrLn $ (replicate (length done + 2) ' ') ++ "^"
                                   bf (commands, '§':done) (ml,mr)
--catch-all pattern. I have yet to discover a way to fall down there
bf _ _ = error "malformed code"

main :: IO ()
main = do
    args <- getArgs
    if length args /= 1 then do
        putStrLn "Usage: brainfuck path"
        exitFailure
    else return ()
    rawProgram <- readFile $ head args
    let program = (filter (`elem` "><+-.,[]#§") rawProgram, [])
    --disable buffering
    hSetBuffering stdout NoBuffering
    hSetBuffering stdin NoBuffering
    --start the interpreter
    bf program defaultMemory