From f9ce1dc5a522e7edd81c3de9e2d2e70beb7fc8f2 Mon Sep 17 00:00:00 2001 From: nibral Date: Wed, 23 Jan 2019 21:42:48 +0900 Subject: [PATCH] 3.8: Implement error handling --- evaluator/evaluator.go | 51 ++++++++++++++++++++++++++----- evaluator/evaluator_test.go | 60 +++++++++++++++++++++++++++++++++++++ object/object.go | 8 +++++ 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 959faf8..a831b87 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -1,6 +1,7 @@ package evaluator import ( + "fmt" "monkey_interpreter/ast" "monkey_interpreter/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) { @@ -23,6 +31,9 @@ func Eval(node ast.Node) object.Object { return evalBlockStatement(node) case *ast.ReturnStatement: val := Eval(node.ReturnValue) + if isError(val) { + return val + } return &object.ReturnValue{Value: val} // expressions @@ -32,10 +43,19 @@ func Eval(node ast.Node) object.Object { return nativeBoolToBooleanObject(node.Value) 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) case *ast.IfExpression: return evalIfExpression(node) @@ -51,8 +71,11 @@ 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 } } @@ -65,8 +88,11 @@ func evalBlockStatement(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 + } } } @@ -80,7 +106,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()) } } @@ -99,7 +125,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 @@ -114,8 +140,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()) } } @@ -141,7 +169,7 @@ 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()) } } @@ -154,6 +182,9 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { func evalIfExpression(ie *ast.IfExpression) object.Object { condition := Eval(ie.Condition) + if isError(condition) { + return condition + } if isTruthy(condition) { return Eval(ie.Consequence) @@ -176,3 +207,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 b8a8633..dab5c4c 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -182,3 +182,63 @@ if (10 > 1) { testIntegerObject(t, evaluated, tt.expected) } } + +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) + } + } +} diff --git a/object/object.go b/object/object.go index 930105a..f7de5c9 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 { @@ -41,3 +42,10 @@ type ReturnValue struct { func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } 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 }