おめが言語

昨日の話ですが。
なんというかこう、あんまり言及したくないような残念な感じのハッカソンに参加してきました。

作った物はコレです。*1

ΩΩωΩΩΩΩΩΩΩωωΩΩωωωωωωωωωωωΩΩΩΩΩωΩΩωωΩΩωωωωωωΩΩΩωΩΩωωωωωωωωΩωΩΩωωΩΩωωωΩωωΩΩΩΩΩΩΩΩωΩωωωΩωωΩΩΩΩΩΩΩΩωΩ

ええと、このファイルを適当なファイルに保存して実行すると、「ABC」と表示される残念な処理系を開発しました。
所謂「難解プログラミング言語」という奴ですね。

本当は「Hello,World!」とかなんか表示させたかったのですが、極めて残念な事に時間が足りなかったので「ABC」と表示させるのが精一杯という残念な結果になってしまいました。*2

説明とか

基本的にはプログラムカウンタを含む4つのレジスタと整数値のアドレスを持ったメモリ(という名のマップ)を持った残念な感じのオレオレレジスタマシンです。

  • プログラムは実行前にメモリに読み込まれます。
  • 命令は1つのオペレータと2つのオペランドを組にした、3つの整数値からなります。
  • Ωとωを交互に記述し、同じ文字が連続して書かれた個数-1が一つの値にパースされます。

例えば「ΩΩωΩΩΩ」というコードは「1,0,2」と解釈されメモリ上にロードされ、「Aレジスタに2をセットする」という意味になるというワケです。残念ですね。
さらに残念なお知らせがあります。分岐命令を制限時間内に実装する事ができなかったため、現段階ではチューリング完全じゃ無いです。
さらに、メモリの読み書きもまだ実装できていません。プログラムカウンタを除いた3つのレジスタで頑張ってください。*3本当に、残念に思っています。

細かい説明は面倒なので完成させたら書きます。大急ぎで書いたgdgdな事この上ない、とても残念なコードを下に載せておくので、適当に読んだりいじったり遊んだりしても良いですが、そんな残念な事をしている暇があったら勉強なり仕事なりしたほうが有意義かと思います。

コード

本当は色々修正かけてからこの記事を書くのもアリかなぁと思ったのですが、フェアじゃないのは残念な気がしてきたので、6時間分の残念な成果物をそのまんま晒しておきます。

Reg.hs

module Reg(runReg, runProgram) where
import Control.Monad.State
import Data.Char
import qualified Data.Map as M

-- ================================================
---------------------------------------------------

--レジスタ
data Reg = Reg {
    regA  :: Int,
    regB  :: Int,
    regC  :: Int,
    regPC :: Int
  } deriving Show
data RegState = RegState { reg :: Reg , mem :: M.Map Int Int } deriving Show

-- ================================================
---------------------------------------------------

--レジスタに値入れたり
regSet :: Int -> Int -> Reg -> Reg  
regSet 0 x (Reg a b c pc) = Reg x b c pc
regSet 1 x (Reg a b c pc) = Reg a x c pc
regSet 2 x (Reg a b c pc) = Reg a b x pc
regSet 3 x (Reg a b c pc) = Reg a b c x

--レジスタから取ったり
regGet :: Int -> Reg -> Int
regGet 0 r = regA r
regGet 1 r = regB r
regGet 2 r = regC r
regGet 3 r = regPC r

--レジスタ間の値移動
regMov :: Int -> Int -> Reg -> Reg
regMov x y r = regSet x (regGet y r) r

---------------------------------------------------

--演算
regCalc :: (Int -> Int -> Int) -> Int -> Int -> Reg -> Reg 
regCalc f x y r =
  let v = (regGet x r) `f` (regGet y r)
  in regSet x v r
--足し算
regAdd :: Int -> Int -> Reg -> Reg
regAdd = regCalc (+)
--引き算
regSub :: Int -> Int -> Reg -> Reg
regSub = regCalc (-)
--かけ算
regMul :: Int -> Int -> Reg -> Reg
regMul = regCalc (*)
--わり算
regDiv :: Int -> Int -> Reg -> Reg
regDiv = regCalc div
--剰余
regMod :: Int -> Int -> Reg -> Reg
regMod = regCalc mod

-- ================================================
---------------------------------------------------

rReg :: State RegState Reg
rReg = do
  state <- get
  return $ reg state

rCalc :: (Int -> Int -> Reg -> Reg) -> Int -> Int -> String -> State RegState String
rCalc f x y s = do
  state <- get
  r <- rReg
  put $ RegState (f x y r) (mem state)
  return s

rRegRead :: Int -> State RegState Int
rRegRead i = do
  state <- get
  m <- return $ mem state
  x <- return $ M.lookup i m
  return $ nothingIsZero x
   where
    nothingIsZero (Just x) = x
    nothingIsZero Nothing = 0

---------------------------------------------------

rSet :: Int -> Int -> String -> State RegState String
rSet x y s = do
  state <- get
  r <- rReg
  put $ RegState (regSet x y r) (mem state)
  return s

--足し算
rAdd :: Int -> Int -> String -> State RegState String
rAdd = rCalc regAdd
--引き算
rSub :: Int -> Int -> String -> State RegState String
rSub = rCalc regSub
--かけ算
rMul :: Int -> Int -> String -> State RegState String
rMul = rCalc regMul
--わり算
rDiv :: Int -> Int -> String -> State RegState String
rDiv = rCalc regDiv
--剰余
rMod :: Int -> Int -> String -> State RegState String
rMod = rCalc regMod

---------------------------------------------------
--出力
rPut :: Int -> Int -> String -> State RegState String
rPut i 0 s = do
  r <- rReg
  return $ chr (regGet i r) : s
rPut i 1 s = do
  r <- rReg
  return $ (reverse.show $ regGet i r) ++ s

---------------------------------------------------
--命令コードから関数を返す
getOrder :: Int -> (Int -> Int -> String -> State RegState String)
getOrder 1 = rSet
getOrder 2 = rAdd
getOrder 3 = rSub
getOrder 4 = rMul
getOrder 5 = rDiv
getOrder 6 = rMod
getOrder 7 = rPut

-- ================================================
---------------------------------------------------
--初期状態

initReg :: Reg
initReg = Reg 0 0 0 0

initRegState :: [(Int,Int)] -> RegState
initRegState init = RegState initReg (M.fromList init)

runReg :: State RegState a -> [(Int,Int)] -> (a, RegState)
runReg f init = runState f (initRegState init)

-- ================================================
---------------------------------------------------

runProgram :: String -> State RegState String
runProgram res = do
  r <- rReg
  pc <- return $ regGet 3 r
  odr <- rRegRead pc
  x <- rRegRead (pc + 1)
  y <- rRegRead (pc + 2)
  if odr == 0 then return res
   else (getOrder odr) x y res >>= rSet 3 (pc + 3) >>= runProgram 

Main.hs

module Main where
import Data.List
import Reg
import System

odrSplit :: Eq a => [a] -> [Int]
odrSplit = map (\l -> (length l) - 1) . group

makeOrder :: String -> [(Int,Int)]
makeOrder odr = zip [0..] (odrSplit odr)

main :: IO ()
main = do
  fname <- getArgs
  program <- readFile (head fname)
  putStrLn . reverse . fst $ run program
    where run prog = runReg (runProgram "") (makeOrder prog)

ところで

Main.hsをちゃんと読むと気づくと思うんですが、今のところこの「おめが言語」で使う文字は「Ω、ω」でなくても良かったりします。
ぶっちゃけ「0、1」でも「A、B」でも「残、念」でも、なんでも良いです。別に2文字じゃなくても大丈夫です。
これもフィルタリングしてる余裕が無かったので後回しにしたからこうなってるわけですが、おかげ様で本当に残念さが出せているんじゃないかと感じています。

まぁ、でも、6時間みっちりHaskell書けるような機会ってなかなか作れないので、楽しかったです。また次回も参加したいと思います。*4
うん、残念

*1:何故「Ω」なのかは、内輪ネタという事で。

*2:さらに残念な事にそもそも持っていったPCがプロジェクタに繋がらなくてマトモに発表すらできなかったわけですが。

*3:プログラムはメモリに読み込まれ、さらにそのメモリが読み書きできるという事は、自分自身を改変するプログラムも書けるようになるという事ですよ!おめが言語のクセに!

*4:プロジェクターの問題についてはどうにかする気すらありませんが