Browse Source

Compiling Expression -- executing jumps

simon 3 years ago
parent
commit
d640582c4a
5 changed files with 54 additions and 15 deletions
  1. 3 0
      code/code.go
  2. 10 12
      compiler/compiler.go
  3. 7 3
      compiler/compiler_test.go
  4. 25 0
      vm/vm.go
  5. 9 0
      vm/vm_test.go

+ 3 - 0
code/code.go

@@ -80,6 +80,8 @@ const (
 
 	OpJumpNotTruthy
 	OpJump
+
+	OpNull
 )
 
 // Definition For debugging and testing purposes
@@ -110,6 +112,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) {

+ 10 - 12
compiler/compiler.go

@@ -120,17 +120,15 @@ func (c *Compiler) Compile(node ast.Node) error {
 			c.removeLastPop()
 		}
 
-		if node.Alternative == nil {
-			afterConsequencePos := len(c.instructions)
-
-			c.changOperand(jumpNotTruthyPos, afterConsequencePos)
-		} else {
-			// Emit an `OpJump` with a bogus value
-			jumpPos := c.emit(code.OpJump, 9999)
+		// Emit an `OpJump` with a bogus value
+		jumpPos := c.emit(code.OpJump, 9999)
 
-			afterConsequencePos := len(c.instructions)
-			c.changOperand(jumpNotTruthyPos, afterConsequencePos)
+		afterConsequencePos := len(c.instructions)
+		c.changOperand(jumpNotTruthyPos, afterConsequencePos)
 
+		if node.Alternative == nil {
+			c.emit(code.OpNull)
+		} else {
 			err := c.Compile(node.Alternative)
 			if err != nil {
 				return err
@@ -138,10 +136,10 @@ func (c *Compiler) Compile(node ast.Node) error {
 			if c.lastInstructionIsPop() {
 				c.removeLastPop()
 			}
-
-			afterAlternativePos := len(c.instructions)
-			c.changOperand(jumpPos, afterAlternativePos)
 		}
+
+		afterAlternativePos := len(c.instructions)
+		c.changOperand(jumpPos, afterAlternativePos)
 	case *ast.BlockStatement:
 		for _, s := range node.Statements {
 			err := c.Compile(s)

+ 7 - 3
compiler/compiler_test.go

@@ -195,14 +195,18 @@ func TestConditionals(t *testing.T) {
 				// 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.OpJump, 11),
+				// 0010
+				code.Make(code.OpNull),
+				// 0011
 				code.Make(code.OpPop),
-				// 0008
+				// 0012
 				code.Make(code.OpConstant, 1),
-				// 0011
+				// 0015
 				code.Make(code.OpPop),
 			},
 		},

+ 25 - 0
vm/vm.go

@@ -16,6 +16,7 @@ const StackSize = 2048
 
 var True = &object.Boolean{Value: true}
 var False = &object.Boolean{Value: false}
+var Null = &object.Null{}
 
 type VM struct {
 	constants    []object.Object
@@ -88,12 +89,36 @@ func (vm *VM) Run() error {
 			}
 		case code.OpPop:
 			vm.pop()
+		case code.OpJump:
+			pos := int(code.ReadUint16(vm.instructions[ip+1:]))
+			ip = pos - 1
+		case code.OpJumpNotTruthy:
+			pos := int(code.ReadUint16(vm.instructions[ip+1:]))
+			ip += 2
+
+			condition := vm.pop()
+			if !isTruthy(condition) {
+				ip = pos - 1
+			}
+		case code.OpNull:
+			err := vm.push(Null)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
 	return nil
 }
 
+func isTruthy(obj object.Object) bool {
+	switch obj := obj.(type) {
+	case *object.Boolean:
+		return obj.Value
+	default:
+		return true
+	}
+}
 func (vm *VM) push(o object.Object) error {
 	if vm.sp >= StackSize {
 		return fmt.Errorf("stack overflow")

+ 9 - 0
vm/vm_test.go

@@ -61,6 +61,15 @@ func TestBooleanExpression(t *testing.T) {
 		{"!!true", true},
 		{"!!false", false},
 		{"!!5", true},
+		{"if (true) { 10 };", 10},
+		{"if (true) { 10 } else { 20 };", 10},
+		{"if (false) { 10 } else { 20 };", 20},
+		{"if (1) {10}", 10},
+		{"if (1 < 2) {10}", 10},
+		{"if (1 < 2) {10} else {20}", 10},
+		{"if (1 > 2) {10} else {20}", 20},
+		{"if (false) {10;}", Null},
+		{"if (1 > 2) {10;}", Null},
 	}
 
 	runVmTests(t, tests)