Files
bf-interpreter/app/Main.hs
2025-09-24 19:02:03 +08:00

210 lines
7.5 KiB
Haskell

{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Control.Arrow ((>>>))
import Control.Monad (void)
import Data.Bits (shiftR, (.&.))
import Data.ByteArray qualified as BA
import Data.Char (chr, ord)
import Data.Functor (($>))
import Data.Int (Int8)
import Data.Kind (Type)
import Data.Vector qualified as V
import Data.Vector.Storable.Mutable qualified as MV
import Data.Word (Word16, Word8)
import Foreign.Ptr (Ptr, castPtr, minusPtr, plusPtr)
import Foreign.Storable qualified as S
import System.Environment (getArgs, getProgName)
import System.Exit (exitFailure)
import System.IO qualified as IO
import Text.ParserCombinators.ReadP qualified as P
class Interpreter a where
data Program a :: Type
parse :: String -> Program a
interpret :: Memory -> Program a -> IO ()
newtype Memory = Memory {unMemory :: MV.IOVector Int8}
type MemIdx = Int
newMemory :: Int -> IO Memory
newMemory = fmap Memory . MV.new
memorySize :: Memory -> Int
memorySize = MV.length . unMemory
readMemory :: Memory -> MemIdx -> IO Int8
readMemory = MV.unsafeRead . unMemory
writeMemory :: Memory -> MemIdx -> Int8 -> IO ()
writeMemory = MV.unsafeWrite . unMemory
modifyMemory :: Memory -> (Int8 -> Int8) -> MemIdx -> IO ()
modifyMemory = MV.unsafeModify . unMemory
nextMemoryIndex :: Memory -> MemIdx -> MemIdx
nextMemoryIndex memory memIdx = (memIdx + 1) `mod` memorySize memory
prevMemoryIndex :: Memory -> MemIdx -> MemIdx
prevMemoryIndex memory memIdx = (memIdx - 1) `mod` memorySize memory
-- StringInterpreter with char zipper
data StringInterpreter
instance Interpreter StringInterpreter where
data Program StringInterpreter = ProgramCZ CharZipper
parse = ProgramCZ . czFromString
interpret memory (ProgramCZ code) = interpretCharZipper memory code
-- the structure is [...,3,2,1,0] focus [0,1,2,3,...]
data CharZipper = CharZipper
{czLeft :: String, czFocus :: Maybe Char, czRight :: String}
czFromString :: String -> CharZipper
czFromString = \case
[] -> CharZipper [] Nothing []
(x : xs) -> CharZipper [] (Just x) xs
czMoveLeft :: CharZipper -> CharZipper
czMoveLeft = \case
CharZipper [] (Just focus) right -> CharZipper [] Nothing (focus : right)
CharZipper (x : xs) (Just focus) right -> CharZipper xs (Just x) (focus : right)
z -> z
czMoveRight :: CharZipper -> CharZipper
czMoveRight = \case
CharZipper left (Just focus) [] -> CharZipper (focus : left) Nothing []
CharZipper left (Just focus) (x : xs) -> CharZipper (focus : left) (Just x) xs
z -> z
interpretCharZipper :: Memory -> CharZipper -> IO ()
interpretCharZipper memory = go 0
where
go !memIdx !program = case czFocus program of
Nothing -> return ()
Just c -> case c of
'+' -> modifyMemory memory (+ 1) memIdx >> goNext
'-' -> modifyMemory memory (subtract 1) memIdx >> goNext
'>' -> go (nextMemoryIndex memory memIdx) program'
'<' -> go (prevMemoryIndex memory memIdx) program'
',' -> do
getChar >>= writeMemory memory memIdx . fromIntegral . ord
goNext
'.' -> do
readMemory memory memIdx >>= putChar . chr . fromIntegral
goNext
'[' -> readMemory memory memIdx >>= \case
0 -> go memIdx $ skipRight 1 program
_ -> goNext
']' -> readMemory memory memIdx >>= \case
0 -> goNext
_ -> go memIdx $ skipLeft 1 program
_ -> goNext
where
program' = czMoveRight program
goNext = go memIdx program'
skipRight :: Int -> CharZipper -> CharZipper
skipRight !depth !program
| depth == 0 = program'
| otherwise = case czFocus program' of
Nothing -> error "No matching [ while skipping the loop forward"
Just '[' -> skipRight (depth + 1) program'
Just ']' -> skipRight (depth - 1) program'
_ -> skipRight depth program'
where
program' = czMoveRight program
skipLeft :: Int -> CharZipper -> CharZipper
skipLeft !depth !program
| depth == 0 = czMoveRight program
| otherwise = case czFocus program' of
Nothing -> error "No matching ] while skipping the loop backward"
Just ']' -> skipLeft (depth + 1) program'
Just '[' -> skipLeft (depth - 1) program'
_ -> skipLeft depth program'
where
program' = czMoveLeft program
-- AST
data ASTInterpreter
instance Interpreter ASTInterpreter where
data Program ASTInterpreter = ProgramAST Instructions
parse = ProgramAST . parseToInstructions
interpret memory (ProgramAST instrs) = interpretAST memory instrs
type Instructions = V.Vector Instruction
data Instruction
= Inc -- +
| Dec -- -
| MoveR -- >
| MoveL -- <
| GetC -- ,
| PutC -- .
| Loop Instructions -- []
deriving (Show)
parseToInstructions :: String -> Instructions
parseToInstructions code =
V.fromList $ case P.readP_to_S (P.many instrParser <* P.eof) code of
[(res, "")] -> res
out -> error $ "Unexpected output while parsing: " <> show out
where instrParser = P.choice
[
P.char '+' $> Inc,
P.char '-' $> Dec,
P.char '>' $> MoveR,
P.char '<' $> MoveL,
P.char ',' $> GetC,
P.char '.' $> PutC,
Loop . V.fromList <$> P.between (P.char '[') (P.char ']') (P.many instrParser)
]
interpretAST :: Memory -> Instructions -> IO ()
interpretAST memory = void . interpretInstrs 0 memory
interpretInstrs :: MemIdx -> Memory -> Instructions -> IO MemIdx
interpretInstrs memIdx !memory !program = go memIdx 0
where
go !memIdx !progIdx
| progIdx == V.length program = return memIdx
| otherwise = case program V.! progIdx of
Inc -> modifyMemory memory (+ 1) memIdx >> goNext
Dec -> modifyMemory memory (subtract 1) memIdx >> goNext
MoveR -> go (nextMemoryIndex memory memIdx) $ progIdx + 1
MoveL -> go (prevMemoryIndex memory memIdx) $ progIdx + 1
GetC -> do
getChar >>= writeMemory memory memIdx . fromIntegral . ord
goNext
PutC -> do
readMemory memory memIdx >>= putChar . chr . fromIntegral
goNext
Loop instrs -> readMemory memory memIdx >>= \case
0 -> goNext
_ -> interpretInstrs memIdx memory instrs >>= flip go progIdx
where
goNext = go memIdx $ progIdx + 1
main :: IO ()
main = do
IO.hSetBuffering IO.stdin IO.NoBuffering
IO.hSetBuffering IO.stdout IO.LineBuffering
progName <- getProgName
let usage = "Usage: " <> progName <> " -(s|a|b|o) <bf_file>"
getArgs >>= \case
[interpreterType, fileName] -> do
code <- filter (`elem` "+-.,><[]") <$> readFile fileName
memory <- newMemory 30000
parseAndInterpret memory code usage interpreterType
_ -> exitWithMsg usage
where
parseAndInterpret memory code usage = \case
"-s" -> interpret @StringInterpreter memory $ parse code
"-a" -> interpret @ASTInterpreter memory $ parse code
-- "-b" -> interpret @BytecodeInterpreter memory $ parse code
-- "-o" -> interpret @OptimizingBytecodeInterpreter memory $ parse code
t -> exitWithMsg $ "Invalid interpreter type: " <> t <> "\n" <> usage
exitWithMsg msg = IO.hPutStrLn IO.stderr msg >> exitFailure