From b47a39e1b2289183de25bcd0e1670fdf8296b6c2 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Mon, 18 Mar 2024 16:17:13 -0400 Subject: [PATCH] Greater than and Less Than --- code/code.go | 68 +++++++++++++++++++------------------ compiler/compiler.go | 10 ++++-- compiler/compiler_test.go | 20 +++++++++++ evaluator/evaluator.go | 4 +++ evaluator/evaluator_test.go | 4 +++ lexer/lexer.go | 16 +++++++-- parser/parser.go | 4 +++ parser/parser_test.go | 8 +++++ token/token.go | 6 ++-- vm/vm.go | 15 +++++--- vm/vm_test.go | 4 +++ 11 files changed, 116 insertions(+), 43 deletions(-) diff --git a/code/code.go b/code/code.go index 368de8d..a7551a1 100644 --- a/code/code.go +++ b/code/code.go @@ -22,6 +22,7 @@ const ( OpEqual OpNotEqual OpGreaterThan + OpGreaterThanEqual OpMinus OpBang OpJumpNotTruthy @@ -52,39 +53,40 @@ type Definition struct { } var definitions = map[Opcode]*Definition{ - OpConstant: {"OpConstant", []int{2}}, - OpAssignGlobal: {"OpAssignGlobal", []int{2}}, - OpAssignLocal: {"OpAssignLocal", []int{1}}, - 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{}}, - OpMinus: {"OpMinus", []int{}}, - OpBang: {"OpBang", []int{}}, - OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, - OpJump: {"OpJump", []int{2}}, - OpNull: {"OpNull", []int{}}, - OpGetGlobal: {"OpGetGlobal", []int{2}}, - OpSetGlobal: {"OpSetGlobal", []int{2}}, - OpArray: {"OpArray", []int{2}}, - OpHash: {"OpHash", []int{2}}, - OpIndex: {"OpIndex", []int{}}, - OpCall: {"OpCall", []int{1}}, - OpReturnValue: {"OpReturnValue", []int{}}, - OpReturn: {"OpReturn", []int{}}, - OpGetLocal: {"OpGetLocal", []int{1}}, - OpSetLocal: {"OpSetLocal", []int{1}}, - OpGetBuiltin: {"OpGetBuiltin", []int{1}}, - OpClosure: {"OpClosure", []int{2, 1}}, - OpGetFree: {"OpGetFree", []int{1}}, - OpCurrentClosure: {"OpCurrentClosure", []int{}}, - OpNoop: {"OpNoop", []int{}}, + OpConstant: {"OpConstant", []int{2}}, + OpAssignGlobal: {"OpAssignGlobal", []int{2}}, + OpAssignLocal: {"OpAssignLocal", []int{1}}, + 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{}}, + OpGreaterThanEqual: {"OpGreaterThanEqual", []int{}}, + OpMinus: {"OpMinus", []int{}}, + OpBang: {"OpBang", []int{}}, + OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, + OpJump: {"OpJump", []int{2}}, + OpNull: {"OpNull", []int{}}, + OpGetGlobal: {"OpGetGlobal", []int{2}}, + OpSetGlobal: {"OpSetGlobal", []int{2}}, + OpArray: {"OpArray", []int{2}}, + OpHash: {"OpHash", []int{2}}, + OpIndex: {"OpIndex", []int{}}, + OpCall: {"OpCall", []int{1}}, + OpReturnValue: {"OpReturnValue", []int{}}, + OpReturn: {"OpReturn", []int{}}, + OpGetLocal: {"OpGetLocal", []int{1}}, + OpSetLocal: {"OpSetLocal", []int{1}}, + OpGetBuiltin: {"OpGetBuiltin", []int{1}}, + OpClosure: {"OpClosure", []int{2, 1}}, + OpGetFree: {"OpGetFree", []int{1}}, + OpCurrentClosure: {"OpCurrentClosure", []int{}}, + OpNoop: {"OpNoop", []int{}}, } func Lookup(op byte) (*Definition, error) { diff --git a/compiler/compiler.go b/compiler/compiler.go index 0a3201b..d794ec5 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -81,7 +81,7 @@ func (c *Compiler) Compile(node ast.Node) error { } case *ast.InfixExpression: - if node.Operator == "<" { + if node.Operator == "<" || node.Operator == "<=" { err := c.Compile(node.Right) if err != nil { return err @@ -91,7 +91,11 @@ func (c *Compiler) Compile(node ast.Node) error { if err != nil { return err } - c.emit(code.OpGreaterThan) + if node.Operator == "<=" { + c.emit(code.OpGreaterThanEqual) + } else { + c.emit(code.OpGreaterThan) + } return nil } @@ -116,6 +120,8 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(code.OpDiv) case ">": c.emit(code.OpGreaterThan) + case ">=": + c.emit(code.OpGreaterThanEqual) case "==": c.emit(code.OpEqual) case "!=": diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 3c768a6..f7d9f84 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -120,6 +120,26 @@ func TestBooleanExpressions(t *testing.T) { 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.OpGreaterThanEqual), + 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.OpGreaterThanEqual), + code.Make(code.OpPop), + }, + }, { input: "1 == 2", expectedConstants: []interface{}{1, 2}, diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index e81dc74..775c6f3 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -296,8 +296,12 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje return &object.Integer{Value: leftVal / rightVal} case "<": return nativeBoolToBooleanObject(leftVal < rightVal) + case "<=": + return nativeBoolToBooleanObject(leftVal <= rightVal) case ">": return nativeBoolToBooleanObject(leftVal > rightVal) + case ">=": + return nativeBoolToBooleanObject(leftVal >= rightVal) case "==": return nativeBoolToBooleanObject(leftVal == rightVal) case "!=": diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 98257b8..7afd780 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -62,6 +62,10 @@ func TestEvalBooleanExpression(t *testing.T) { {"(1 < 2) == false", false}, {"(1 > 2) == true", false}, {"(1 > 2) == false", true}, + {"(1 <= 2) == true", true}, + {"(1 <= 2) == false", false}, + {"(1 >= 2) == true", false}, + {"(1 >= 2) == false", true}, {`"a" == "a"`, true}, {`"a" < "b"`, true}, {`"abc" == "abc"`, true}, diff --git a/lexer/lexer.go b/lexer/lexer.go index 56c09c7..c31d041 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -84,9 +84,21 @@ func (l *Lexer) NextToken() token.Token { case '*': tok = newToken(token.ASTERISK, l.ch) case '<': - tok = newToken(token.LT, l.ch) + if l.peekChar() == '=' { + l.readChar() + tok = newToken(token.LTE, l.ch) + tok.Literal = "<=" + } else { + tok = newToken(token.LT, l.ch) + } case '>': - tok = newToken(token.GT, l.ch) + if l.peekChar() == '=' { + l.readChar() + tok = newToken(token.GTE, l.ch) + tok.Literal = ">=" + } else { + tok = newToken(token.GT, l.ch) + } case ';': tok = newToken(token.SEMICOLON, l.ch) case ',': diff --git a/parser/parser.go b/parser/parser.go index c6a269c..10fa5d7 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -25,6 +25,8 @@ var precedences = map[token.TokenType]int{ token.NOT_EQ: EQUALS, token.LT: LESSGREATER, token.GT: LESSGREATER, + token.LTE: LESSGREATER, + token.GTE: LESSGREATER, token.PLUS: SUM, token.MINUS: SUM, token.SLASH: PRODUCT, @@ -78,7 +80,9 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.EQ, p.parseInfixExpression) p.registerInfix(token.NOT_EQ, p.parseInfixExpression) p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.LTE, p.parseInfixExpression) p.registerInfix(token.GT, p.parseInfixExpression) + p.registerInfix(token.GTE, p.parseInfixExpression) p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression) diff --git a/parser/parser_test.go b/parser/parser_test.go index 0a96c13..af88119 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -285,6 +285,14 @@ func TestOperatorPrecedenceParsing(t *testing.T) { "5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))", }, + { + "5 >= 4 == 3 <= 4", + "((5 >= 4) == (3 <= 4))", + }, + { + "5 <= 4 != 3 >= 4", + "((5 <= 4) != (3 >= 4))", + }, { "3 + 4 * 5 == 3 * 1 + 4 * 5", "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", diff --git a/token/token.go b/token/token.go index 256ec7c..bacf912 100644 --- a/token/token.go +++ b/token/token.go @@ -27,8 +27,10 @@ const ( ASTERISK = "*" SLASH = "/" - LT = "<" - GT = ">" + LT = "<" + LTE = "<=" + GT = ">" + GTE = ">=" EQ = "==" NOT_EQ = "!=" diff --git a/vm/vm.go b/vm/vm.go index f39349e..d7407d8 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -83,11 +83,14 @@ func (vm *VM) Run() error { if vm.Debug { log.Printf( - "%-20s %-20s\n", - strings.Split(ins[ip:].String(), "\n")[0], + "%-25s %-20s\n", + fmt.Sprintf( + "%04d %s", ip, + strings.Split(ins[ip:].String(), "\n")[0][4:], + ), fmt.Sprintf( "[ip=%02d fp=%02d, sp=%02d]", - ip, vm.sp, vm.framesIndex-1, + ip, vm.framesIndex-1, vm.sp, ), ) } @@ -130,7 +133,7 @@ func (vm *VM) Run() error { return err } - case code.OpEqual, code.OpNotEqual, code.OpGreaterThan: + case code.OpEqual, code.OpNotEqual, code.OpGreaterThan, code.OpGreaterThanEqual: err := vm.executeComparison(op) if err != nil { return err @@ -512,6 +515,8 @@ func (vm *VM) executeIntegerComparison(op code.Opcode, left, right object.Object return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) case code.OpGreaterThan: return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + case code.OpGreaterThanEqual: + return vm.push(nativeBoolToBooleanObject(leftValue >= rightValue)) default: return fmt.Errorf("unknown operator: %d", op) } @@ -628,6 +633,8 @@ func (vm *VM) executeStringComparison(op code.Opcode, left, right object.Object) return vm.push(nativeBoolToBooleanObject(rightValue != leftValue)) case code.OpGreaterThan: return vm.push(nativeBoolToBooleanObject(leftValue > rightValue)) + case code.OpGreaterThanEqual: + return vm.push(nativeBoolToBooleanObject(leftValue >= rightValue)) default: return fmt.Errorf("unknown operator: %d", op) } diff --git a/vm/vm_test.go b/vm/vm_test.go index 9bdfcc1..15a7281 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -209,6 +209,10 @@ func TestIntegerArithmetic(t *testing.T) { {"(1 < 2) == false", false}, {"(1 > 2) == true", false}, {"(1 > 2) == false", true}, + {"(1 <= 2) == true", true}, + {"(1 <= 2) == false", false}, + {"(1 >= 2) == true", false}, + {"(1 >= 2) == false", true}, {"-5", -5}, {"-10", -10}, {"-50 + 100 + -50", 0},