んだ日記

ndaDayoの技術日記です

Writing A Compiler In Go を読んでいく Chapter 6 Index編

こんにちは、んだです。

本日も『Writing A Compiler In Go』です。

nda-desu.hatenablog.com

前回はHashについて解説しましたので今回は6章最後のIndexです。

Index

まずは、Indexの例についてみていきます。

[1, 2, 3][1]
{"one": 1, "two": 2, "three": 3}["o" + "ne"]

ArrayとHashからIndexを指定して、値を取得するというのが今回やりたいことです。

Compiler

はじめに、Compilerについてみていきます

// compiler/compiler.go
    case *ast.IndexExpression:
        err := c.Compile(node.Left)

        if err != nil {
            return err
        }

        err = c.Compile(node.Index)
        if err != nil {
            return err
        }

        c.emit(code.OpIndex)
    }

まずは、node.Leftをコンパイルします。[1, 2, 3][1]でいうと、[1, 2, 3]ですね。 続いて、node.Indexをコンパイルします。

VM

次に、VMの処理を追ってみましょう。

compilerで見た通り、Leftを先、Indexを後にコンパイルしているで、Popするときには、逆にindexを先にleftを後にpopするわけですね。

// 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.OpIndex:
            index := vm.pop()
            left := vm.pop()

            err := vm.executeIndexExpression(left, index)
            if err != nil {
                return err
            }

executeIndexExpression

で、popしたindexとleftはVM上どう処理されているのでしょうか??

executeIndexExpressionでは、ArrayとHashで分岐しているだけですね。executeArrayIndexとexecuteHashIndexを見てみましょう。

func (vm *VM) executeIndexExpression(left, index object.Object) error {
    switch {
    case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
        return vm.executeArrayIndex(left, index)
    case left.Type() == object.HASH_OBJ:
        return vm.executeHashIndex(left, index)
    default:
        return fmt.Errorf("index operator not supported: %s", left.Type())
    }
}

executeArrayIndex

Arrayはこんな感じです。

indexの値をi < 0 || i > maxで、有効な値かどうかチャックして、配列から取得できない値であればNull値をプッシュしてますね。

有効なIndexであれば、そのまま値を配列から取得してpushです。

func (vm *VM) executeArrayIndex(array, index object.Object) error {
    arrayObject := array.(*object.Array)
    i := index.(*object.Integer).Value

    max := int64(len(arrayObject.Elements) - 1)

    if i < 0 || i > max {
        return vm.push(Null)
    }

    return vm.push(arrayObject.Elements[i])
}

executeHashIndex

続いては、Hashです。 HashについてもArrayと同様に、指定されたIndexがHashから取得できるかを確認した上で、有効なIndexでなければNull値をPush。

有効であれば、そのままValueをPushです。

func (vm *VM) executeHashIndex(hash, index object.Object) error {
    hashObject := hash.(*object.Hash)

    key, ok := index.(object.Hashable)

    if !ok {
        return fmt.Errorf("unusable as hash key: %s", index.Type())
    }

    pair, ok := hashObject.Pairs[key.HashKey()]
    if !ok {
        return vm.push(Null)
    }
    return vm.push(pair.Value)
}

まとめ

以上でChapter 6が完了です。

ArrayとHashそしてIndexについてみてきたわけですが、要素要素に分解してみると、一つ一つはやっていることはシンプルかつ明快です。

そうした部品が組み合わさってVMコンパイラが動いておるのを理解すると、ただただ愉しいですね。

次回は、関数です。ワクワクしますね。

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

全体のコードはこちら

github.com