たなかさんのおにくをたべよう

Haskell界の有名なエンジニアで、「すごいH本」ことすごいHaskellたのしく学ぼうの翻訳もされている田中英行さんのご自宅にて、少人数のハッカソン「たなかにくまつり」が開催されたので参加して来ました。

開催されるまでの経緯とか色々あるのですが、とにかくまぁ、尊敬しているHaskellerさんと直接お話できる機会と思いエントリーしたわけですね。決して他意は無いです。


無いですってば。



意気揚々と参戦したものの、やっぱり皆さんレベルが高い高い。結局、話にまともに付いていけたのはモナドに関する話題くらいでして。
しゃーないので、はしっこのほうでタコ焼きをもちゃもちゃ食べながらコードを書いていた次第です。

せっかくFreeモナドの使い方を覚えたので、Parsecの勉強と合わせて簡単なスタック型プログラミング言語を作ってみようと思っていたのですが、流石に1日だけだと構文木インタプリタ処理系を作るのが限界でした。
状態を保持するための領域がスタックだけの言語なので、チューリング完全では無いんじゃないかなぁと思います。

とりあえず、大したものでは無いから、なるべく早いうちに完成させてしまうつもりなので、構文とか言語仕様の話はその時に書くとしましょう。
とりあえず今日はコードだけ貼っておくので、テストコードから雰囲気だけ掴んで貰えれば良いかと思われます。
Freeモナドにすると、do構文でほぼそのままの記述ができるのは嬉しいです。

Language/BricksLang/SyntaxTree.hs:

module Language.BricksLang.SyntaxTree where
import Control.Monad.Free

---------------------------------------------------------------------------------------------------
--構文木データ定義

data Bricks a
  = BricksPush Int a
  | BricksPop a
  | BricksPutChar a
  | BricksPutInt a
  | BricksAdd a
  | BricksEq a
  | BricksLabel String a
  | BricksGoto String a
  | BricksThrow String a
  deriving (Show, Eq)

--Freeモナドで扱うためにFunctorにする
instance Functor Bricks where
  fmap f (BricksPush x n) = BricksPush x (f n) 
  fmap f (BricksPop n) = BricksPop (f n) 
  fmap f (BricksPutChar n) = BricksPutChar (f n) 
  fmap f (BricksPutInt n) = BricksPutInt (f n) 
  fmap f (BricksLabel l n) = BricksLabel l (f n)
  fmap f (BricksAdd n) = BricksAdd (f n) 
  fmap f (BricksEq n) = BricksEq (f n) 
  fmap f (BricksGoto l n) = BricksGoto l (f n) 
  fmap f (BricksThrow m n) = BricksThrow m (f n)

---------------------------------------------------------------------------------------------------
--構文を組み立てるFreeモナド関数化

liftF :: Functor f => f r -> Free f r
liftF cmd = Impure (fmap Pure cmd)

bPush :: Int -> Free Bricks ()
bPush x = liftF $ BricksPush x ()

bPop :: Free Bricks ()
bPop = liftF $ BricksPop ()

bPutChar :: Free Bricks ()
bPutChar = liftF $ BricksPutChar ()

bPutInt :: Free Bricks ()
bPutInt = liftF $ BricksPutInt ()

bLabel :: String -> Free Bricks ()
bLabel m = liftF $ BricksLabel m ()

bGoto :: String -> Free Bricks ()
bGoto m = liftF $ BricksGoto m ()

bAdd :: Free Bricks ()
bAdd = liftF $ BricksAdd ()

bEq :: Free Bricks ()
bEq = liftF $ BricksEq ()

bThrow :: String -> Free Bricks ()
bThrow m = liftF $ BricksThrow m ()

Language/BricksLang/Interpreter.hs:

module Language.BricksLang.Interpreter( 
  module Language.BricksLang.SyntaxTree , execBricks , makeLabelTable ) where
import Language.BricksLang.SyntaxTree 
import Control.Monad.Free
import Data.Char
import qualified Data.Map as M

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

execBricks :: Free Bricks () -> IO ()
execBricks program = runBricks ([], M.fromList (makeLabelTable program)) program

---------------------------------------------------------------------------------------------------
--ラベルテーブル作成
---------------------------------------------------------------------------------------------------

makeLabelTable :: Free Bricks a -> [(String, Free Bricks a)]
makeLabelTable (Pure _) = [] 
makeLabelTable (Impure (BricksLabel a n)) = (a, n) : makeLabelTable n
makeLabelTable (Impure (BricksPush _ n)) = makeLabelTable n
makeLabelTable (Impure (BricksPop n)) = makeLabelTable n
makeLabelTable (Impure (BricksPutChar n)) = makeLabelTable n
makeLabelTable (Impure (BricksPutInt n)) = makeLabelTable n
makeLabelTable (Impure (BricksAdd n)) = makeLabelTable n
makeLabelTable (Impure (BricksEq n)) = makeLabelTable n
makeLabelTable (Impure (BricksThrow _ n)) = makeLabelTable n
makeLabelTable (Impure (BricksGoto _ n)) = makeLabelTable n

---------------------------------------------------------------------------------------------------
--Bricks-langインタプリタ実行
---------------------------------------------------------------------------------------------------

type BricksState a = ([Int], M.Map String (Free Bricks a))

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

runBricks :: BricksState a -> Free Bricks a ->  IO a

-- Push
runBricks (xs, m) (Impure (BricksPush x n)) = runBricks ((x:xs), m) n

-- Pop
runBricks ([], m) (Impure (BricksPop n)) 
  = error "[Bricks-lang runtime error] `Pop` couldn't run because stack is empty"
runBricks ((_:xs), m) (Impure (BricksPop n)) = runBricks (xs, m) n

--PutChar
runBricks ([], _) (Impure (BricksPutChar n)) 
  = error "[Bricks-lang runtime error] `PutChar` couldn't run because stack is empty"
runBricks st@((x:_), m) (Impure (BricksPutChar n)) = putChar (chr x) >> runBricks st n

--PutInt
runBricks ([], _) (Impure (BricksPutInt n)) 
  = error "[Bricks-lang runtime error] `PutInt` couldn't run becaouse stack is empty"
runBricks st@((x:_), m) (Impure (BricksPutInt n)) = (putStr.show) x >> runBricks st n

--Label ・・・何もしない
runBricks st (Impure (BricksLabel _ n)) = runBricks st n

--Throw
runBricks _ (Impure (BricksThrow m _)) = error $ "[Bricks-lang runtime error] EXCEPTION : " ++ m

--Goto
runBricks st@((0:_), m) (Impure (BricksGoto l _)) = callNext $ M.lookup l m
  where
  callNext Nothing = error $ "[Bricks-lang runtime error] `Goto` couldn't find label `" ++ l ++ "`"
  callNext (Just n) =  runBricks st n
runBricks st (Impure (BricksGoto _ n)) = runBricks st n

--Add
runBricks ((x:y:xs), m) (Impure (BricksAdd n)) = runBricks ((x + y:xs) ,m) n
runBricks _ (Impure (BricksAdd _)) = 
  error $ "[Bricks-kang runtime error] `Add` couldn't run becaouse stack is empty"

--Eq
runBricks (xs@(x:y:_), m) (Impure (BricksEq n)) = runBricks ((eq x y:xs) ,m) n
  where eq x y = if x == y then 0 else 1
runBricks _ (Impure (BricksEq _)) = 
  error $ "[Bricks-kang runtime error] `Add` couldn't run becaouse stack is empty"

-- ...Pure
runBricks _ (Pure x) = return x

テストプログラム:

module Test.Interpreter() where
import Language.BricksLang.Interpreter

main = execBricks $ do
  --Loop開始定形処理
  bPush 3 --Loop回数
  bPush 1
  bPush 1
  bLabel "Loop"
  bPop
  bPop

  --主処理
  bPush 66
  bPush 65
  bPutChar --A
  bPop
  bPutChar --B
  bPop

  --Loop終了定形処理
  bPush (-1)
  bAdd
  bPush 0
  bEq
  bPush (-1)
  bAdd
  bGoto "Loop" --Gotoはスタックの一番上の要素が 0 の時だけ実行される
  
  --Loop開始定形処理
  bPush 3 --Loop回数
  bPush 1
  bPush 1
  bLabel "Loop2"
  bPop
  bPop

  --主処理
  bPush 68
  bPush 67
  bPutChar --C
  bPop
  bPutChar --D
  bPop

  --Loop終了定形処理
  bPush (-1)
  bAdd
  bPush 0
  bEq
  bPush (-1)
  bAdd
  bGoto "Loop2"

実行結果:

ABABABCDCDCD

とゆーわけで、楽しい時間ありがとうございましたーm(__)m