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

@snowcrush

動機

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

[1]

SLOC !!

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

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

[2]

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


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

→_まずWhitespace作ってみよう

[3]

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
[4]

インタープリタ

1. パーサ
ソースファイルを読み込んでバイトを1文字ずつ読み取り、命令オブジェクトの配列に変換
2. ランタイム
命令オブジェクトの配列を前から順に実行していく(巻き戻りあり)

[5]

コメント

まずパースする際は、コメントはすべて飛ばす。
空白、タブ、LFを除く全ての文字はコメントである。

[6]

命令

Whitespaceのコードは命令と引数に大別される。

例えば
- SS => 引数をスタックにプッシュ
- SLS => スタックの一番上を複製する

などのように S(空白)、T(タブ)、L(ラインフィード)の組み合わせでそれぞれ命令を構成する

[7]

引数

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

例:
TTTSSSL #=> [1,1,1,0,0,0]

[8]

引数

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

Whitespaceで扱える値は数値型のみである。
ラベルは命令の場所を覚えておくために使う(Goのラベルと同じ)

[9]

パーサ

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

[10]

命令オブジェクト

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

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

[11]

実行

[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]

Thank you