こんにちは、んだです。
本日も『Writing A Compiler In Go』です。
前回は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とコンパイラが動いておるのを理解すると、ただただ愉しいですね。
次回は、関数です。ワクワクしますね。
僕から以上。あったかくして寝ろよ。
全体のコードはこちら