んだ日記

ndaDayoの技術日記です

Writing A Compiler In Go を読んでいく Chapter 4 Executing Jumps

こんにちは!んだです。

今日も今日とて『Writing A Compiler In Go』の読書録を書いていきます。 ほんなごて、コンパイラは楽しいですね。

前回は、Conditionalのcompilerについて眺めていきましたので、今回はVMについてみていきます。Executing Jumps!!です。

TestCase

まずは、いつものようにTestCaseを眺めてまいりましょう。

func TestConditionals(t *testing.T) {
    tests := []vmTestCase{
        {"if (true) { 10 }", 10},
        {"if (true) { 10 } else { 20 }", 10},
        {"if (false) { 10 } else { 20 } ", 20},
        {"if (1) { 10 }", 10},
        {"if (1 < 2) { 10 }", 10},
        {"if (1 < 2) { 10 } else { 20 }", 10},
        {"if (1 > 2) { 10 } else { 20 }", 20},
    }

    runVmTests(t, tests)
}

if文が並んでおりますね。 では、どうやってVM上で処理していくかをみていきましょう。

OpJump

まずは、OpJumpから

// vm/vm.go
func (vm *VM) Run() error {
    for ip := 0; ip < len(vm.instructions); ip++ {
        op := code.Opcode(vm.instructions[ip])

        switch op {
        //  [....]
        case code.OpJump:
            pos := int(code.ReadUnit16(vm.instructions[ip+1:]))
            ip = pos - 1

ip = pos - 1は、何をしているのでしょうか? OpJumpでは、Jump先まで命令を進めたいわけですが、ip = pos - 1と一つポジションをずらしておかないと、次のループに入った時にインクリメントされていまい、Jump先の一個に進んでしまいます。

そのため、ip = pos - 1で一つずらしておくわけですね。

OpJumpNotTruthy

続いては、OpJumpNotTruthyです。

// vm/vm.go
func (vm *VM) Run() error {
    for ip := 0; ip < len(vm.instructions); ip++ {
        op := code.Opcode(vm.instructions[ip])

        switch op {
        //  [....]
        case code.OpJumpNotTruthy:
            pos := int(code.ReadUnit16(vm.instructions[ip+1:]))
            ip += 2

            condition := vm.pop()
            if !isTruthy(condition) {
                ip = pos - 1
            }

テストケースを眺めながら追ってみましょう。

inputとして

if (true) { 10 } else { 20 };

があったときに、Bytecodeは以下のように生成されます。

0000:code.Make(code.OpTrue),
0001:code.Make(code.OpJumpNotTruthy, 10),
0004:code.Make(code.OpConstant, 0),
0007:code.Make(code.OpJump, 13),
0010:code.Make(code.OpConstant, 1), 
0013:code.Make(code.OpPop),

まずは、以下でジャンプ先を取得して、posに入れておきます。続いて、オペランド分ipを移動します。

pos := int(code.ReadUnit16(vm.instructions[ip+1:]))
ip += 2

次は、条件式をPopしてきて真偽値を判定します。

falseの場合には、ip = pos - 1としてあげることで、0009からスタートすることにます。

ループ時に1つipがインクリメントされるので、ジャンプ先である0010に移動できるというわけです。

condition := vm.pop()
if !isTruthy(condition) {
    ip = pos - 1
}

ということでテストも通りまして、条件分岐も突破です。

まとめ

以上で条件分岐も完了です。

前回までの中置式や前置式と違って、JUMP命令がある分やや複雑ではあるのですが、一つ一つみていくとやっていることはシンプルです。

次回は5章となり後半です。

どんな実装が出てくるか、興奮してきますね。

僕から以上。あったかくして寝ろよ。

github.com