I just started learning Haskell and this is my first big project (ie not factorials, fibonacci or graphers). This is kind of a gift for somebody so the language is a bit different. The program works, but I felt that some parts of the code could be improved, specifically:
- Is Vector the best choice for representing current memory state in the program state record,
Program
? - I executed the program by splitting the source code into a list of words (the code state), then recursively going through the list and
parse
ing it on each loop. Is there a better way to do it? Tokenization maybe? - I created loops by putting the current code state into a stack named
loopback
, and using this to "go back in time" when needed. Is there a better way to do it? - I just felt the way I handled loops are inelegant; such as
toChaCharmanderChar'
. - Did I use guards and pattern matching correctly?
- Is it confusing in general?
I consider myself a beginner-intermediate programmer, having just learnt monads.
Documentation
Char: Previous 1
Char-char: Next 1
Cha: Input
Charmander: Output
Cha-cha: Minus 1
Charmander-charmander: Plus 1
Charmander-char: Loop start; if data at DP is 0, jump to corresponding Cha-charmander-char.
Cha-charmander-char: Loop end; if data at DP is not 0, jump to corresponding Charmander-char.
DP: Data Pointer, starts at 0. Points to an integer in memory.
Memory: A tape of 128 integers.
Main.hs
module Main
(
main
) where
import Data.Vector (Vector, replicate, (//), (!), accum)
import Data.Char (ord, chr, toLower)
import System.Environment (getArgs)
import System.IO (IOMode(ReadMode), BufferMode(NoBuffering), openFile, hGetContents, hSetBuffering, stdin)
-- Program state
type DP = Integer
type Memory = Vector Integer
type Code = [String]
data Program = Program {
dp :: DP,
memory :: Memory,
code :: Code,
loopback :: [Code]
} deriving Show
initial :: Code -> Program
initial code = Program {
dp = 0,
memory = Data.Vector.replicate 128 0,
code = code,
loopback = []
}
-- Microcommands
next :: Program -> Program
next program = program {
code = tail $ code program
}
move :: Integer -> Program -> Program
move num program = program {
dp = dp program + num
}
change :: Integer -> Program -> Program
change num program = program {
memory = accum (+) (memory program) [(fromInteger $ dp program, num)]
}
toChaCharmanderChar' :: Integer -> Program -> Program
toChaCharmanderChar' depth program
| depth == 0 && current == "cha-charmander-char" = program
| current == "cha-charmander-char" = toChaCharmanderChar' (depth - 1) $ next program
| current == "charmander-char" = toChaCharmanderChar' (depth + 1) $ next program
| otherwise = toChaCharmanderChar' depth $ next program
where current = head $ code program
toChaCharmanderChar :: Program -> Program
toChaCharmanderChar = toChaCharmanderChar' 0
-- Commands
char :: Program -> IO Program
char program = return $ move (-1) program
charChar :: Program -> IO Program
charChar program = return $ move 1 program
chaCha :: Program -> IO Program
chaCha program = return $ change (-1) program
charmanderCharmander :: Program -> IO Program
charmanderCharmander program = return $ change 1 program
cha :: Program -> IO Program
cha program = do
input <- getChar
return program {
memory = (memory program) // [(fromInteger $ dp program, toInteger $ ord input)]
}
charmander :: Program -> IO Program
charmander program = do
putStr [chr $ fromInteger $ (memory program) ! (fromInteger $ dp program)]
return program
charmanderChar :: Program -> IO Program
charmanderChar program
| num == 0 = return $ toChaCharmanderChar $ next program
| num /= 0 = return program {
loopback = code program : loopback program
}
where num = memory program ! (fromInteger $ dp program)
chaCharmanderChar :: Program -> IO Program
chaCharmanderChar program
| num == 0 = return program {
loopback = tail $ loopback program
}
| num /= 0 = return program {
code = head $ loopback program
}
where num = memory program ! (fromInteger $ dp program)
unknown :: Program -> IO Program
unknown program = return program
-- Parser
parse :: String -> Program -> IO Program
parse "char" = char
parse "char-char" = charChar
parse "cha" = cha
parse "charmander" = charmander
parse "cha-cha" = chaCha
parse "charmander-charmander" = charmanderCharmander
parse "charmander-char" = charmanderChar
parse "cha-charmander-char" = chaCharmanderChar
parse _ = unknown
-- Interpreter
interpret' :: Program -> IO ()
interpret' program
| code' == [] = do
putStrLn "\nCore dump: "
print program
putStrLn "\nCha char charmander (0)."
| otherwise = do
newProgram <- parse (head code') program
interpret' $ next newProgram
where code' = code program
interpret :: String -> IO ()
interpret program = interpret' $ initial $ words $ fmap toLower program
-- Main
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
args <- getArgs
if null args
then
putStrLn "Cha charmander (-1)."
else do
handle <- openFile (head args) ReadMode
contents <- hGetContents handle
interpret contents
charmander.cabal
-- Initial charmander.cabal generated by cabal init. For further
-- documentation, see http://haskell.org/cabal/users-guide/
name: charmander
version: 0.1.0.0
synopsis: Charmander-char!
-- description:
-- license:
license-file: LICENSE
author: Ignis Incendio
maintainer: [email protected]
-- copyright:
category: Language
build-type: Simple
cabal-version: >=1.8
executable charmander
main-is: Main.hs
-- other-modules:
build-depends: base ==4.6.*, split, vector
Sample programs
A Loop.char
Charmander-charmander charmander-charmander charmander-charmander charmander-charmander charmander-charmander
Charmander-charmander charmander-charmander charmander-charmander charmander-charmander charmander-charmander
Charmander-char
Char-char
Charmander-charmander charmander-charmander charmander-charmander
Charmander-charmander charmander-charmander charmander-charmander
Char
Cha-cha
Cha-charmander-char
Char-char
Charmander-charmander charmander-charmander charmander-charmander charmander-charmander charmander-charmander
Charmander
Input.char
Cha <- This means to input into 0
charmander <- This outputs the 0
OOB.char
OOB since DP will be at -1 and then try to print it
Char cha charmander
Output
ignis99:~/workspace/charmander $ dist/build/charmander/charmander A\ Loop.char
A
Core dump:
Program {dp = 1, memory = fromList [0,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], code = [], loopback = []}
Cha char charmander (0).
ignis99:~/workspace/charmander $ dist/build/charmander/charmander Input.char
qq
Core dump:
Program {dp = 0, memory = fromList [113,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], code = [], loopback = []}
Cha char charmander (0).
ignis99:~/workspace/charmander $ dist/build/charmander/charmander OOB.char
qcharmander: ./Data/Vector/Generic/Mutable.hs:730 (update): index out of bounds (-1,128)