diff --git a/code/code.go b/code/code.go index c45c15f..dbd596c 100644 --- a/code/code.go +++ b/code/code.go @@ -26,6 +26,7 @@ const ( OpBang OpJumpNotTruthy OpJump + OpNull ) type Definition struct { @@ -49,6 +50,7 @@ var definitions = map[Opcode]*Definition{ OpBang: {"OpBang", []int{}}, OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, OpJump: {"OpJump", []int{2}}, + OpNull: {"OpNull", []int{}}, } func Lookup(op byte) (*Definition, error) { diff --git a/compiler/compiler.go b/compiler/compiler.go index 0d41081..cbe2dd3 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -134,16 +134,15 @@ func (c *Compiler) Compile(node ast.Node) error { c.removeLastPop() } + // Emit an `OnJump` with a bogus value + jumpPos := c.emit(code.OpJump, 9999) + + afterConsequencePos := len(c.instructions) + c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + if node.Alternative == nil { - afterConsequencePos := len(c.instructions) - c.changeOperand(jumpNotTruthyPos, afterConsequencePos) + c.emit(code.OpNull) } else { - // Emit an `OnJump` with a bogus value - jumpPos := c.emit(code.OpJump, 9999) - - afterConsequencePos := len(c.instructions) - c.changeOperand(jumpNotTruthyPos, afterConsequencePos) - err = c.Compile(node.Alternative) if err != nil { return err @@ -152,12 +151,11 @@ func (c *Compiler) Compile(node ast.Node) error { if c.lastInstructionIsPop() { c.removeLastPop() } - - afterAlternativePos := len(c.instructions) - c.changeOperand(jumpPos, afterAlternativePos) - } + afterAlternativePos := len(c.instructions) + c.changeOperand(jumpPos, afterAlternativePos) + case *ast.BlockStatement: for _, s := range node.Statements { err := c.Compile(s) diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 5a92360..9d2d9fb 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -182,17 +182,22 @@ func TestConditionals(t *testing.T) { `, expectedConstants: []interface{}{10, 3333}, expectedInstructions: []code.Instructions{ + // 0000 code.Make(code.OpTrue), // 0001 - code.Make(code.OpJumpNotTruthy, 7), + code.Make(code.OpJumpNotTruthy, 10), // 0004 code.Make(code.OpConstant, 0), // 0007 - code.Make(code.OpPop), - // 0008 - code.Make(code.OpConstant, 1), + code.Make(code.OpJump, 11), + // 0010 + code.Make(code.OpNull), // 0011 code.Make(code.OpPop), + // 0012 + code.Make(code.OpConstant, 1), + // 0015 + code.Make(code.OpPop), }, }, { input: ` diff --git a/vm/vm.go b/vm/vm.go index c1ae856..fd6e449 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -9,6 +9,7 @@ import ( const StackSize = 2048 +var Null = &object.Null{} var True = &object.Boolean{Value: true} var False = &object.Boolean{Value: false} @@ -99,6 +100,12 @@ func (vm *VM) Run() error { if !isTruthy(condition) { ip = pos - 1 } + + case code.OpNull: + err := vm.push(Null) + if err != nil { + return err + } } } @@ -111,6 +118,9 @@ func isTruthy(obj object.Object) bool { case *object.Boolean: return obj.Value + case *object.Null: + return false + default: return true } @@ -211,6 +221,8 @@ func (vm *VM) executeBangOperator() error { return vm.push(False) case False: return vm.push(True) + case Null: + return vm.push(True) default: return vm.push(False) } diff --git a/vm/vm_test.go b/vm/vm_test.go index 4af2505..3821b80 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -54,6 +54,11 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object if err != nil { t.Errorf("testBooleanObject failed: %s", err) } + + case *object.Null: + if actual != Null { + t.Errorf("object is not Null: %T (%+v)", actual, actual) + } } } @@ -138,6 +143,7 @@ func TestBooleanExpressions(t *testing.T) { {"!!true", true}, {"!!false", false}, {"!!5", true}, + {"!(if (false) { 5; })", true}, } runVmTests(t, tests) @@ -152,6 +158,9 @@ func TestConditionals(t *testing.T) { {"if (1 < 2) { 10 }", 10}, {"if (1 < 2) { 10 } else { 20 }", 10}, {"if (1 > 2) { 10 } else { 20 }", 20}, + {"if (1 > 2) { 10 }", Null}, + {"if (false) { 10 }", Null}, + {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, } runVmTests(t, tests)