こんばんわ、んだです。
本日も『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章からは、ジャンプ命令に突入です。
興奮してきますね。
僕から以上。あったかくして寝ろよ。