From 44612f07565442a0704500e7834c0a22987ae868 Mon Sep 17 00:00:00 2001 From: tijani Date: Thu, 11 Aug 2022 06:55:07 +0000 Subject: [PATCH] Error handling git-svn-id: https://svn.tlawal.org/svn/monkey@50 f6afcba9-9ef1-4bdd-9b72-7484f5705bac --- evaluator/evaluator.go | 55 +++++++++++++++++++++++++++++++------ evaluator/evaluator_test.go | 36 ++++++++++++++++++++++++ object/object.go | 9 ++++++ 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index eadad71..884787e 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -1,6 +1,7 @@ package evaluator import ( + "fmt" "monkey/ast" "monkey/object" ) @@ -29,11 +30,21 @@ func Eval(node ast.Node) object.Object { case *ast.PrefixExpression: right := Eval(node.Right) + if is_error(right){ + return right + } return eval_prefix_expression(node.Operator, right) case *ast.InfixExpression: left := Eval(node.Left) + if is_error(left){ + return left + } + right := Eval(node.Right) + if is_error(right){ + return right + } return eval_infix_expression(node.Operator, left, right) case *ast.BlockStatement: @@ -44,6 +55,9 @@ func Eval(node ast.Node) object.Object { case *ast.ReturnStatement: val := Eval(node.ReturnValue) + if is_error(val){ + return val + } return &object.ReturnValue{Value: val} } @@ -56,8 +70,12 @@ func eval_program(program *ast.Program) object.Object { for _, statement := range program.Statements { result = Eval(statement) - if return_value, ok := result.(*object.ReturnValue); ok { - return return_value.Value + switch result := result.(type) { + case *object.ReturnValue: + return result.Value + + case *object.Error: + return result } } return result @@ -69,8 +87,11 @@ func eval_block_statement(block *ast.BlockStatement) object.Object { for _, statement := range block.Statements { result = Eval(statement) - if result != nil && result.Type() == object.RETURN_VALUE_OBJECT { - return result + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJECT || rt == object.ERROR_OBJECT { + return result + } } } @@ -92,7 +113,7 @@ func eval_prefix_expression(operator string, right object.Object) object.Object return eval_minus_prefix_operator_expression(right) default: - return NULL + return new_error("unknown operator: %s%s", operator, right.Type()) } } @@ -114,7 +135,7 @@ func eval_bang_operator_expression(right object.Object) object.Object { func eval_minus_prefix_operator_expression(right object.Object) object.Object { if right.Type() != object.INTEGER_OBJECT { - return NULL + return new_error("unknown operator: -%s", right.Type()) } value := right.(*object.Integer).Value return &object.Integer{Value: -value} @@ -130,8 +151,11 @@ func eval_infix_expression(operator string, left object.Object, right object.Obj case operator == "!=": return native_bool_to_boolean_object(left != right) + + case left.Type() != right.Type(): + return new_error("type mismatch: %s %s %s", left.Type(), operator, right.Type()) default: - return NULL + return new_error("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } @@ -165,13 +189,17 @@ func eval_integer_infix_expression(operator string, left object.Object, right ob return native_bool_to_boolean_object(left_value != right_value) default: - return NULL + return new_error("unknown operator: %s %s %s", left.Type(), operator, right.Type()) } } func eval_if_expression(ie *ast.IfExpression) object.Object { condition := Eval(ie.Condition) + if is_error(condition){ + return condition + } + if is_truthy(condition) { return Eval(ie.Consequence) } else if ie.Alternative != nil { @@ -196,3 +224,14 @@ func is_truthy(object object.Object) bool { return true } } + +func new_error(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +func is_error(l_object object.Object) bool { + if l_object != nil { + return l_object.Type() == object.ERROR_OBJECT + } + return false +} diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 6985b88..ee6b70e 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -36,6 +36,42 @@ func TestEvalIntegerExpression(l_test *testing.T) { } } +func TestErrorHandling(l_test *testing.T) { + tests := []struct { + input string + expected_message 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 := test_eval(tt.input) + + error_object, ok := evaluated.(*object.Error) + if !ok { + l_test.Errorf("no error object returned, got=%T(%+v)", evaluated, evaluated) + continue + } + + if error_object.Message != tt.expected_message { + l_test.Errorf("wrong error message, expected=%q, got=%q", tt.expected_message, error_object.Message) + } + } +} + func TestEvalBooleanExpression(l_test *testing.T) { tests := []struct { input string diff --git a/object/object.go b/object/object.go index fde670c..d894232 100644 --- a/object/object.go +++ b/object/object.go @@ -9,6 +9,7 @@ const ( BOOLEAN_OBJECT = "BOOLEAN" NULL_OBJECT = "NULL" RETURN_VALUE_OBJECT = "RETURN_VALUE" + ERROR_OBJECT = "ERROR" ) type Object interface { @@ -60,3 +61,11 @@ type ReturnValue struct { func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJECT } func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } + +// Error +type Error struct { + Message string +} + +func (err *Error) Type() ObjectType { return ERROR_OBJECT } +func (err *Error) Inspect() string { return "ERROR: " + err.Message }