今日で最後だ。のこりの命令「[」と「]」を実装した。
「[」と「]」を実装するには命令の列を行ったり来たりできなきゃいけない。はじめに考えたのは BrainF_ck と同じように,命令のリストと現在位置を示すポインタを持ったデータ型を定義することだった。
けど,考え直して文字列のペアで代用することにした。これを次のように操作する。
- 初期状態では,1番目の文字列は空。全て2番目に入っている。
- 1つ命令を実行するごとに,2番目の文字列から1番目の文字列へ1文字移動する。スキップするには必要な数だけ移動。
- 2番目の文字列が空になったらプログラム終了。
- ループで戻るときには,2番目から1番目へ文字を移動する。
で,実装はこのとおり。
type Program = ([Char], [Char])
progNew :: String -> Program
progNew str = ([], str)
progFetch :: Program -> [Char]
progFetch prog = snd prog
progShift :: Program -> Program
progShift (l, (r:rs)) = (l ++ [r], rs)
progUnshift :: Program -> Program
progUnshift (l, r) = (take ((length l) -1) l, (last l):r)
progSkip :: BrainF_ck -> Program -> Program
progSkip bf prog = if bfValue bf == 0 then skip prog else progShift prog
where skip p = if (head $ progFetch next) == ']' then next else skip next
where next = progShift p
progBack :: BrainF_ck -> Program -> Program
progBack bf prog = if bfValue bf == 0 then progShift prog else back prog
where back p = if (head $ progFetch prev) == '[' then prev else back prev
where prev = progUnshift p
でもって,プログラムを走らせる関数 bfRun を導入(前回までは foldM を使っていた)して,main も書き換えた。
bfRun :: BrainF_ck -> Program -> IO BrainF_ck
bfRun bf prog = run $ progFetch prog
where run [] = return bf
run (c:cs) | c == '[' = bfRun bf (progSkip bf prog)
| c == ']' = bfRun bf (progBack bf prog)
| otherwise = do next <- bfEvaluate bf c
bfRun next (progShift prog)
main :: IO BrainF_ck
main = do filename <- getArgs >>= return . head
prog <- readFile filename >>= ((return . progNew) . concat) . lines
bfRun bfInitial prog
さらに。いままで入出力を整数でやっていたけど,これを文字に変更する。まぁ文字<->整数(文字コード)の変換だけだけど。
import Data.Char
bfInput :: BrainF_ck -> IO BrainF_ck
bfInput bf = do let p = bfPointer bf
let r = bfRegister bf
putStr "\ninput? ";
c <- getChar
return $ BF p ((take p r) ++ [ord c] ++ (tail $ drop p r))
bfPrint :: BrainF_ck -> IO BrainF_ck
bfPrint bf = do putChar $ chr $ bfValue bf
return bf
これでできあがりのはずだ。さっそく試してみよう。
実行するプログラムは当初の目標,HelloWorld。
>type hello.bf ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<< +++++++++++++++.>.+++.------.--------.>+.>. >runghc hbf.hs hello.bf Hello World!
OK!!!!!!!!!!!!!!!!
いろいろと制限もあるけど,とにかく目標は達成。これ以上はまた気が向いたらってことで。
以下,全体を示しておく。
module Main where
import System
import Monad
import Data.Char
data BrainF_ck = BF { bfPointer :: Int, bfRegister :: [Int] } deriving (Show)
type Program = ([Char], [Char])
bfInitial :: BrainF_ck
bfInitial = BF { bfPointer = 0, bfRegister = [0,0,0,0,0,0,0,0,0,0] }
bfValue :: BrainF_ck -> Int
bfValue bf = (bfRegister bf) !! (bfPointer bf)
bfIncrement :: BrainF_ck -> BrainF_ck
bfIncrement (BF p v) = BF p ((take p v) ++ [(v !! p) + 1] ++ (tail $ drop p v))
bfDecrement :: BrainF_ck -> BrainF_ck
bfDecrement (BF p v) = BF p ((take p v) ++ [(v !! p) - 1] ++ (tail $ drop p v))
bfShift :: BrainF_ck -> BrainF_ck
bfShift (BF p v) = BF (p+1) v
bfUnshift :: BrainF_ck -> BrainF_ck
bfUnshift (BF p v) = BF (p-1) v
bfInput :: BrainF_ck -> IO BrainF_ck
bfInput bf = do let p = bfPointer bf
let r = bfRegister bf
putStr "\ninput? "
c <- getChar
return $ BF p ((take p r) ++ [ord c] ++ (tail $ drop p r))
bfPrint :: BrainF_ck -> IO BrainF_ck
bfPrint bf = do putChar $ chr $ bfValue bf
return bf
bfEvaluate :: BrainF_ck -> Char -> IO BrainF_ck
bfEvaluate bf '+' = return $ bfIncrement bf
bfEvaluate bf '-' = return $ bfDecrement bf
bfEvaluate bf '>' = return $ bfShift bf
bfEvaluate bf '<' = return $ bfUnshift bf
bfEvaluate bf '.' = bfPrint bf
bfEvaluate bf ',' = bfInput bf
progNew :: String -> Program
progNew str = ([], str)
progFetch :: Program -> [Char]
progFetch prog = snd prog
progShift :: Program -> Program
progShift (l, (r:rs)) = (l ++ [r], rs)
progUnshift :: Program -> Program
progUnshift (l, r) = (take ((length l) -1) l, (last l):r)
progSkip :: BrainF_ck -> Program -> Program
progSkip bf prog = if bfValue bf == 0 then skip prog else progShift prog
where skip p = if (head $ progFetch next) == ']' then next else skip next
where next = progShift p
progBack :: BrainF_ck -> Program -> Program
progBack bf prog = if bfValue bf == 0 then progShift prog else back prog
where back p = if (head $ progFetch prev) == '[' then prev else back prev
where prev = progUnshift p
bfRun :: BrainF_ck -> Program -> IO BrainF_ck
bfRun bf prog = run $ progFetch prog
where run [] = return bf
run (c:cs) | c == '[' = bfRun bf (progSkip bf prog)
| c == ']' = bfRun bf (progBack bf prog)
| otherwise = do next <- bfEvaluate bf c
bfRun next (progShift prog)
main :: IO BrainF_ck
main = do filename <- getArgs >>= return . head
prog <- readFile filename >>= ((return . progNew) . concat) . lines
bfRun bfInitial prog