こんにちは、んだです。
今回は、『Writing A Compiler In Go 』のChapter 6、String, Array and Hashについて書いていきます。
Array
Stringについては、IntergerLiteralとやっていることが同じなので、Arrayから見ていきます。
またArrayのcompilerについては、Arrayのオブジェクトを走査しておるだけなのでCompileの説明は省きまして、VMから眺めていきましょう。
Array VM
// 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.OpArray: numElements := int(code.ReadUnit16(vm.instructions[ip+1:])) ip += 2 array := vm.buildArray(vm.sp-numElements, vm.sp) vm.sp = vm.sp - numElements err := vm.push(array) if err != nil { return err }
やっていることは他のOpcodeと同じです。異なるのは、buildArrayです。
buildArray
では、buildArrayの中身を見てみましょう
// vm/vm.go func (vm *VM) buildArray(startIndex, endIndex int) object.Object { elements := make([]object.Object, endIndex-startIndex) for i := startIndex; i < endIndex; i++ { elements[i-startIndex] = vm.stack[i] } return &object.Array{Elements: elements} }
startIndex, endIndex
まずは、startIndex, endIndexにはどんな値が入るのか?から確認しましょう。
// vm/vm.go numElements := int(code.ReadUnit16(vm.instructions[ip+1:])) array := vm.buildArray(vm.sp-numElements, vm.sp)
まずは、numElementsに配列の要素数を入れます。 仮に[1, 2, 3, 4] という配列だったとすると、要素数は5ですね。
で、配列の要素はすべてOpConstantなので、OpConstant命令を処理するタイミングでスタックには5個積まれるので、vm.spの値は5です。
startIndexは、vm.sp-numElementsで0が入り、endIndexは、vm.spの現在の値なので5が入るという感じですね。
stackから値を取り出そう
buildArrayの本体に戻りましょう。 startIndex, endIndexですが、以下のようにstackからの値を取り出す際に利用されます。
stackから値を取り出して、Elementsに詰めて返しています。
// vm/vm.go for i := startIndex; i < endIndex; i++ { elements[i-startIndex] = vm.stack[i] } return &object.Array{Elements: elements} }
spを要素分減らして、Push
最後にspを要素分減らして、Pushして完了です。
// vm/vm.go case code.OpArray: numElements := int(code.ReadUnit16(vm.instructions[ip+1:])) ip += 2 array := vm.buildArray(vm.sp-numElements, vm.sp) vm.sp = vm.sp - numElements err := vm.push(array)
まとめ
今回はArrayのみをまとめてみました。
ArrayはVMでどのように処理するのか?と、はじめは想像できませんでしたが、コードを追ってみると、比較的に単純な処理で出来ていますね。
再帰はすばらしいですな。
では、次回はHashについてまとめていきます。
僕から以上。あったかくして寝ろよ