んだ日記

ndaDayoの技術日記です

Writing A Compiler In Go を読んでいく Chapter 3 Prefix Expressions

こんばんわ、んだです。

本日も『Writing A Compiler In Go』を読んでいくシリーズです。

前回は、Chapter 3 Infix Expressionsということで中置式まで書いていきましたので、今回はPrefix Expressionsのコードを追っかけていきたいと思います。

Monkey supports the two prefix operators - and !.

まずは、前置式のoperatorについて確認しましょう
Monkey言語では、minus(-)とbang(!)のみをサポートしてます。

定義としては、こうなります。

// code/code.go

const (
    OpConstant Opcode = iota
         // 中略
    OpMinus
    OpBang
)

var definitions = map[Opcode]*Definition{
   // 中略
    OpMinus:         {"OpMinus", []int{}},
    OpBang:          {"OpBang", []int{}},
}

Compiler TestCase

では、いつものようにテストケースを眺めてみましょう。 Monkey本はTDDで進んでいくので、とても理解しやすいですし、テンポがよくて楽しいです(N回目

// compiler/compiler_test.go

func TestIntegerArithmetic(t *testing.T) { tests := []compilerTestCase{
    tests := []compilerTestCase{
            // 中略
        {
            input:             "-1",
            expectedConstants: []interface{}{1},
            expectedInstructions: []code.Instructions{
                code.Make(code.OpConstant, 0),
                code.Make(code.OpMinus),
                code.Make(code.OpPop),
            },
        },
    }

    runCompilerTests(t, tests)
}


func TestBooleanExpressions(t *testing.T) {
    tests := []compilerTestCase{
                 // 中略
        {
            input:             "!true",
            expectedConstants: []interface{}{},
            expectedInstructions: []code.Instructions{
                code.Make(code.OpTrue),
                code.Make(code.OpBang),
                code.Make(code.OpPop),
            },
        },
    }

    runCompilerTests(t, tests)
}

Compiler

テストケースを確認したところで、実装の方を見ていきましょう

func (c *Compiler) Compile(node ast.Node) error {
    switch node := node.(type) {
    // 中略
    case *ast.PrefixExpression:
        err := c.Compile(node.Right)
        if err != nil {
            return err
        }

        switch node.Operator {
        case "!":
            c.emit(code.OpBang)
        case "-":
            c.emit(code.OpMinus)
        default:
            return fmt.Errorf("unknown operator %s", node.Operator)
        }

Operatorに応じて、OpBangとOpMinusを命令をemitしているだけですね。 では、最後にVMの処理を見ていきましょう。

VM TestCase

では、VMのフェーズに入っていきましょう。 minus(-)とbang(!)を含めたテストケースは、こんな感じです。

// vm/vm_test.go

func TestIntegerArithmetic(t *testing.T) {
    tests := []vmTestCase{
        {"-5", -5},
        {"-10", -10},
        {"-50 + 100 + -50", 0},
        {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50},
    }

    runVmTests(t, tests)
}

func TestBooleanExpression(t *testing.T) {
    tests := []vmTestCase{
        {"!true", false},
        {"!false", true},
        {"!5", false},
        {"!!true", true},
        {"!!false", false},
        {"!!5", true},
    }

    runVmTests(t, tests)
} 

では、最後にこれをVM上でどのように処理していくのかを確認していきましょう。

executeBangOperator

まずは、BangOperatorをどう捌くのか?ですが、実装をみてみましょう。

func (vm *VM) executeBangOperator() error {
    operand := vm.pop()

    switch operand {
    case True:
        return vm.push(False)
    case False:
        return vm.push(True)
    default:
        return vm.push(False)
    }
}

booleanを逆転しているだけですね! 面白いのは、defaultでvm.push(False)としているところです。

!5

この式もMonkeyではFalseとして扱うということが記されておるわけですね。

executeMinusOperator

最後は、executeMinusOperatorです。

func (vm *VM) executeMinusOperator() error {
    operand := vm.pop()

    if operand.Type() != object.INTEGER_OBJ {
        return fmt.Errorf("unsupported type for negation: %s", operand.Type())
    }

    value := operand.(*object.Integer).Value
    return vm.push(&object.Integer{Value: -value})
}

シンプルに、Popしてきたobjectをマイナスに置き換えてpushしているだけですね!

まとめ

以上で、Writing A Compiler In Go を読んでいく Chapter 3も終了です。 4章からは、ジャンプ命令に突入です。

興奮してきますね。

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