Whitespace処理系をGo言語で実装してみた

@snowcrush

素振り

GoConのLTに応募したので素振りさせてくださいー

書いてて思ったが素振りって野球エンジニア用語では・・・

[1]

動機

https://www.ipa.go.jp/sec/reports/20180306.html

[2]

SLOC !!

> brainfuckとかwhitespaceみたいな感じで改行文字のみで
> プログラミングする言語作ると生産性めっちゃあがるんじゃねーかな

みたいなことを某所に書いた。

[3]

じゃあ実際に作ってみよう


そもそもWhitespaceのタブかスペースを"\r"に変えればよくね?

→_まずWhitespace作ってみよう

[4]

Whitespace

SS STSL # push 2 to stack
LS # duplicate top of the stack
TSSS # sum top 2 of the stack
TLST # print top of the stack
=> 4
[5]

仕様

まずWhitespaceの本家が無くなっていた。
検索して以下のページを見つけたのでそれを参考に仕様を書き出した。

実装はここでruby版の実装を主に参考にした。
でも大体命令リストだけで作れたのであんまり読んでないです。

あとWebarchiveで検索して本家からHaskell版のソースコードのアーカイブを取得

[6]

命令

Whitespaceのコードは命令と引数に大別される。
ドキュメントによっては命令はIMPとCommandにさらに分割されると書いてあるが、
実装上分ける意味はあまりない。

命令は以下に大別される。

[7]

引数

引数を取る命令に続く文字列は引数として解釈される。
引数の終わりはLFで、LFに達するまでのタブと空白がそれぞれ正負のビットとして解釈される。
つまり一つの引数はビット列である。

引数は数値型かラベルである。

[8]

コメント

空白、タブ、LFを除く全ての文字はコメントである。

[9]

パーサ

パーサは簡易的なステートマシンとして実装した。

_ ST 空白とタブ文字を読み取った状態

switch文を二重に書いて、現在のステートと読み取ったバイトによって次のステートを決定する。
命令の読み取りが完了した場合、
引数がなければそのまま、引数があればLFを読み取るまで引数を読んだあと、
命令オブジェクトをリストに追加する。

[10]

命令オブジェクト

type Command interface {
    AddBitToParam(bool)
    Exec(*Runtime)
    FinishReadParam()
}

上記インターフェースを満たす構造体を命令ごとに作成した。
AddBitToParamとFinishReadParamは引数の読み取りに使う。
Execは命令ごとの処理をランタイムに対して行う。

[11]

ラベル

命令の場所を覚えておくための識別子(ビット列)。
パーサ読み取り時に*ラベル=>命令オブジェクトのindex*がマップとして作成される。
ラベル定義命令(LSS)で定義する(パース時に作成されるためこの命令は何もしない)。
ジャンプ命令(LSL)でラベルにジャンプすることができる。

[12]

ランタイム

ランタイムは以下の構造体である。

type Runtime struct {
    stdin     *bufio.Reader
    stdout    io.Writer
    stack     Stack
    commands  Program
    heap      map[int]int
    labels    map[int]int
    callstack []int
    index     int
}
[13]

実行

以上!

[14]

結果

[15]

感想

[16]

Thank you