From 44d20ba7a02d326705c5bbd46f9abb1fccd0f01b Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Fri, 19 Jan 2024 18:07:54 -0500 Subject: [PATCH] Errors --- evaluator/evaluator.go | 52 +++++++++++++++++++++++++++----- evaluator/evaluator_test.go | 60 +++++++++++++++++++++++++++++++++++++ object/object.go | 17 +++++++++-- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 6792b5b..e9866f3 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -1,6 +1,7 @@ package evaluator import ( + "fmt" "monkey/ast" "monkey/object" ) @@ -11,6 +12,13 @@ var ( FALSE = &object.Boolean{Value: false} ) +func isError(obj object.Object) bool { + if obj != nil { + return obj.Type() == object.ERROR_OBJ + } + return false +} + func Eval(node ast.Node) object.Object { switch node := node.(type) { @@ -29,6 +37,9 @@ func Eval(node ast.Node) object.Object { case *ast.ReturnStatement: val := Eval(node.ReturnValue) + if isError(val) { + return val + } return &object.ReturnValue{Value: val} // Expressions @@ -40,11 +51,20 @@ func Eval(node ast.Node) object.Object { case *ast.PrefixExpression: right := Eval(node.Right) + if isError(right) { + return right + } return evalPrefixExpression(node.Operator, right) case *ast.InfixExpression: left := Eval(node.Left) + if isError(left) { + return left + } right := Eval(node.Right) + if isError(right) { + return right + } return evalInfixExpression(node.Operator, left, right) } @@ -58,9 +78,13 @@ func evalProgram(program *ast.Program) object.Object { for _, statement := range program.Statements { result = Eval(statement) - if returnValue, ok := result.(*object.ReturnValue); ok { - return returnValue.Value + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + case *object.Error: + return result } + } return result @@ -72,8 +96,11 @@ func evalBlockStatements(block *ast.BlockStatement) object.Object { for _, statement := range block.Statements { result = Eval(statement) - if result != nil && result.Type() == object.RETURN_VALUE_OBJ { - return result + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { + return result + } } } @@ -94,7 +121,7 @@ func evalPrefixExpression(operator string, right object.Object) object.Object { case "-": return evalMinusPrefixOperatorExpression(right) default: - return NULL + return newError("unknown operator: %s%s", operator, right.Type()) } } @@ -113,7 +140,7 @@ func evalBangOperatorExpression(right object.Object) object.Object { func evalMinusPrefixOperatorExpression(right object.Object) object.Object { if right.Type() != object.INTEGER_OBJ { - return NULL + return newError("unknown operator: -%s", right.Type()) } value := right.(*object.Integer).Value @@ -128,8 +155,10 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje return nativeBoolToBooleanObject(left == right) case operator == "!=": return nativeBoolToBooleanObject(left != right) + case left.Type() != right.Type(): + return newError("type mismatch: %s %s %s", left.Type(), operator, right.Type()) default: - return NULL + return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } @@ -155,12 +184,15 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje case "!=": return nativeBoolToBooleanObject(leftVal != rightVal) default: - return NULL + return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } func evalIfExpression(ie *ast.IfExpression) object.Object { condition := Eval(ie.Condition) + if isError(condition) { + return condition + } if isTruthy(condition) { return Eval(ie.Consequence) @@ -183,3 +215,7 @@ func isTruthy(obj object.Object) bool { return true } } + +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 4d58edf..335753c 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -139,6 +139,66 @@ func TestReturnStatements(t *testing.T) { } } +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expectedMessage string + }{ + { + "5 + true;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "5 + true; 5;", + "type mismatch: INTEGER + BOOLEAN", + }, + { + "-true", + "unknown operator: -BOOLEAN", + }, + { + "true + false;", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "5; true + false; 5", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + "if (10 > 1) { true + false; }", + "unknown operator: BOOLEAN + BOOLEAN", + }, + { + ` + if (10 > 1) { + if (10 > 1) { + return true + false; + } + + return 1; + } + `, + "unknown operator: BOOLEAN + BOOLEAN", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("no error object returned. got=%T(%+v)", + evaluated, evaluated) + continue + } + + if errObj.Message != tt.expectedMessage { + t.Errorf("wrong error message. expected=%q, got=%q", + tt.expectedMessage, errObj.Message) + } + } +} + func testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) diff --git a/object/object.go b/object/object.go index 3f02ba5..9b8c6fd 100644 --- a/object/object.go +++ b/object/object.go @@ -9,6 +9,7 @@ const ( BOOLEAN_OBJ = "BOOLEAN" NULL_OBJ = "NULL" RETURN_VALUE_OBJ = "RETURN_VALUE" + ERROR_OBJ = "ERROR" ) type Object interface { @@ -52,10 +53,22 @@ type ReturnValue struct { Value Object } -func (rv ReturnValue) Type() ObjectType { +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } -func (rv ReturnValue) Inspect() string { +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +type Error struct { + Message string +} + +func (e *Error) Type() ObjectType { + return ERROR_OBJ +} + +func (e *Error) Inspect() string { + return "Error: " + e.Message +}