Continue(s)

Twitter:@dn0t_ GitHub:@ogrew

Golangで四則演算するだけのコンパイルができた。

🐬はじめに

speakerdeck.com

buiderscon2019でのDQNEOさんのライブコーディングの発表を見て「コンパイラ作るの夢ある〜面白そ〜」となったのでforkして0からやってみました。 といってGoで書いたのでほとんど写経。ちなみに更にその親玉のchibicc8ccなんかの実際のコードも見てみました。

github.com

📝メモ

...
        case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
            intLiteral := readNumber(char)
            token := &Token{
                kind:  "intliteral",
                value: intLiteral,
            }
            tokens = append(tokens, token)
            fmt.Printf(" '%s'", token.value)
        case ';', '+', '-', '*', '/':
            token := &Token{
                kind:  "punct",
                value: string([]byte{char}),
            }
            tokens = append(tokens, token)
            fmt.Printf(" '%s'", token.value)
...

このあたりの部分はByteReaderさんが処理したものをTokenizerさんがTokenごとにうまく分割しているところ。
readNumberメソッドは数字がその後も続くようだったら最後まで見に行くやつ。

...
        switch token.value {
        case "+", "-", "*", "/":
            return &Expr{
                kind:     "binary",
                operator: token.value,
                left:     expr,
                right:    parseUnaryExpr(),
            }
...

このあたりはTokenの2つ目を見た後にそれが演算子Tokenだった場合はそれにさらに続くと期待される数値Tokenをパースさせようとするところ。
parseUnaryExprの中でparseUnaryExprを呼び出して再帰的になっている。

ちなみに余談なんですがアセンブリコードに出てくるmovq addqのqってなんなんだろう?と思って調べてみました。

検索して出てくるサンプルコードを見ていると、movq や addq ではなく mov, add と書かれているものもあります。q は quad word の略で、4 語長つまり 64bit データを扱うことを意味します。x86 では命令にビット長を明示指定することができ、b : byte (8bit)、w : word (16bit)、l : long word (32bit)、q : quad word (64bit) が指定できます。x86 はもともと 16bit CPU である 8086 から出発しているので、16bit を基準の語長(ワードサイズ)としています。

多くの場合、movq と書かず単に mov とだけ書けばアセンブラが自動的に適切な長さを判断してくれるのですが、この資料では長さを明示する方を選びました。

初学者向け x86/MacOSX 64bit アセンブリ

ということでqなくても問題なく動作しました。


root@e1219eb59aea:/mnt# echo -n '4*7' | go run main.go | ./asrun
------------ assembly(a.s) ------------
#Tokens: '4' '*' '7'

  .global main
main:
  mov $4, %rax
  mov $7, %rcx
  imul %rcx, %rax
  ret
------------    result     ------------
28
root@e1219eb59aea:/mnt# echo -n '12/3' | go run main.go | ./asrun
------------ assembly(a.s) ------------
#Tokens: '12' '/' '3'

  .global main
main:
  mov $12, %rax
  mov $3, %rcx
  mov $0, %rdx
  idiv %rcx
  ret
------------    result     ------------
4

てな具合で写経しながらではあるものの四則演算のできるアセンブリコードを吐かせることにも無事成功。

型があるとだいぶ楽ですね。
楽しかったのでまた続きをやる…かも?

参考

github.com

github.com

github.com

github.com