From dcc869a6e299e54bddafb46a7d1b9999356d0ef5 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Mon, 5 Feb 2024 16:58:59 -0500 Subject: [PATCH] comparisons and booleans --- code/code.go | 22 ++++++++--- compiler/compiler.go | 27 +++++++++++++ compiler/compiler_test.go | 83 +++++++++++++++++++++++++++++++++++++++ vm/vm.go | 62 +++++++++++++++++++++++++++++ vm/vm_test.go | 45 +++++++++++++++++++++ 5 files changed, 233 insertions(+), 6 deletions(-) diff --git a/code/code.go b/code/code.go index ce72499..309e73a 100644 --- a/code/code.go +++ b/code/code.go @@ -17,6 +17,11 @@ const ( OpSub OpMul OpDiv + OpTrue + OpFalse + OpEqual + OpNotEqual + OpGreaterThan ) type Definition struct { @@ -25,12 +30,17 @@ type Definition struct { } var definitions = map[Opcode]*Definition{ - OpConstant: {"OpConstant", []int{2}}, - OpAdd: {"OpAdd", []int{}}, - OpPop: {"OpPop", []int{}}, - OpSub: {"OpSub", []int{}}, - OpMul: {"OpMul", []int{}}, - OpDiv: {"OpDiv", []int{}}, + OpConstant: {"OpConstant", []int{2}}, + OpAdd: {"OpAdd", []int{}}, + OpPop: {"OpPop", []int{}}, + OpSub: {"OpSub", []int{}}, + OpMul: {"OpMul", []int{}}, + OpDiv: {"OpDiv", []int{}}, + OpTrue: {"OpTrue", []int{}}, + OpFalse: {"OpFalse", []int{}}, + OpEqual: {"OpEqual", []int{}}, + OpNotEqual: {"OpNotEqual", []int{}}, + OpGreaterThan: {"OpGreaterThan", []int{}}, } func Lookup(op byte) (*Definition, error) { diff --git a/compiler/compiler.go b/compiler/compiler.go index 9d45ffc..05d7460 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -37,6 +37,20 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(code.OpPop) case *ast.InfixExpression: + if node.Operator == "<" { + err := c.Compile(node.Right) + if err != nil { + return err + } + + err = c.Compile(node.Left) + if err != nil { + return err + } + c.emit(code.OpGreaterThan) + return nil + } + err := c.Compile(node.Left) if err != nil { return err @@ -56,6 +70,12 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(code.OpMul) case "/": c.emit(code.OpDiv) + case ">": + c.emit(code.OpGreaterThan) + case "==": + c.emit(code.OpEqual) + case "!=": + c.emit(code.OpNotEqual) default: return fmt.Errorf("unknown operator %s", node.Operator) } @@ -64,6 +84,13 @@ func (c *Compiler) Compile(node ast.Node) error { integer := &object.Integer{Value: node.Value} c.emit(code.OpConstant, c.addConstant(integer)) + case *ast.Boolean: + if node.Value { + c.emit(code.OpTrue) + } else { + c.emit(code.OpFalse) + } + } return nil diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 95635bf..a009ed7 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -73,6 +73,89 @@ func TestIntegerArithmetic(t *testing.T) { runCompilerTests(t, tests) } +func TestBooleanExpressions(t *testing.T) { + tests := []compilerTestCase{ + { + input: "true", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpPop), + }, + }, + { + input: "false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpFalse), + code.Make(code.OpPop), + }, + }, + { + input: "1 > 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 < 2", + expectedConstants: []interface{}{2, 1}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpGreaterThan), + code.Make(code.OpPop), + }, + }, + { + input: "1 == 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "1 != 2", + expectedConstants: []interface{}{1, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true == false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpEqual), + code.Make(code.OpPop), + }, + }, + { + input: "true != false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpNotEqual), + code.Make(code.OpPop), + }, + }, + } + + runCompilerTests(t, tests) +} + func runCompilerTests(t *testing.T, tests []compilerTestCase) { t.Helper() diff --git a/vm/vm.go b/vm/vm.go index 2e1998b..6e2b8cb 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -9,6 +9,9 @@ import ( const StackSize = 2048 +var True = &object.Boolean{Value: true} +var False = &object.Boolean{Value: false} + type VM struct { constants []object.Object instructions code.Instructions @@ -53,6 +56,24 @@ func (vm *VM) Run() error { case code.OpPop: vm.pop() + + case code.OpTrue: + err := vm.push(True) + if err != nil { + return err + } + + case code.OpFalse: + err := vm.push(False) + if err != nil { + return err + } + + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + err := vm.executeComparison(op) + if err != nil { + return err + } } } @@ -111,3 +132,44 @@ func (vm *VM) executeBinaryIntegerOperation(op code.Opcode, left, right object.O return vm.push(&object.Integer{Value: result}) } + +func (vm *VM) executeComparison(op code.Opcode) error { + right := vm.pop() + left := vm.pop() + + if left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ { + return vm.executeIntegerComparison(op, left, right) + } + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(right == left)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(right != left)) + default: + return fmt.Errorf("unknown operator: %d (%s %s)", op, left.Type(), right.Type()) + } +} + +func (vm *VM) executeIntegerComparison(op code.Opcode, left, right object.Object) error { + leftValue := left.(*object.Integer).Value + rightValue := right.(*object.Integer).Value + + switch op { + case code.OpEqual: + return vm.push(nativeBoolToBooleanObject(rightValue == leftValue)) + case code.OpNotEqual: + return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) + case code.OpGreaterThan: + return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + default: + return fmt.Errorf("unknown operator: %d", op) + } +} + +func nativeBoolToBooleanObject(input bool) *object.Boolean { + if input { + return True + } + return False +} diff --git a/vm/vm_test.go b/vm/vm_test.go index 2495a2b..612de94 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -48,6 +48,12 @@ func testExpectedObject(t *testing.T, expected interface{}, actual object.Object if err != nil { t.Errorf("testIntegerObject failed: %s", err) } + + case bool: + err := testBooleanObject(bool(expected), actual) + if err != nil { + t.Errorf("testBooleanObject failed: %s", err) + } } } @@ -70,6 +76,19 @@ func testIntegerObject(expected int64, actual object.Object) interface{} { return nil } +func testBooleanObject(expected bool, actual object.Object) interface{} { + result, ok := actual.(*object.Boolean) + if !ok { + return fmt.Errorf("object is not Boolean. got=%T (%+v", actual, actual) + } + + if result.Value != expected { + return fmt.Errorf("object has wrong value. got=%t, want=%t", result.Value, expected) + } + + return nil +} + func TestIntegerArithmetic(t *testing.T) { tests := []vmTestCase{ {"1", 1}, @@ -83,6 +102,32 @@ func TestIntegerArithmetic(t *testing.T) { {"5 * 2 + 10", 20}, {"5 + 2 * 10", 25}, {"5 * (2 + 10)", 60}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + } + + runVmTests(t, tests) +} + +func TestBooleanExpressions(t *testing.T) { + tests := []vmTestCase{ + {"true", true}, + {"false", false}, } runVmTests(t, tests)