umx_compiler

UMX virtual machine "Monkey" interpreter / bytecode compiler
git clone git://bsandro.tech/umx_compiler
Log | Files | Refs

commit 073b20492e0f7bb8c9b8e93b013a8ac68e66cd6d
parent d879cb5ebc90f1eb8925311f0695c77534f69c10
Author: bsandro <email@bsandro.tech>
Date:   Fri, 24 Jun 2022 01:51:14 +0300

eval error handing

Diffstat:
Meval/eval.go | 52+++++++++++++++++++++++++++++++++++++++++++---------
Meval/eval_test.go | 24++++++++++++++++++++++++
Mobject/object.go | 8++++++++
3 files changed, 75 insertions(+), 9 deletions(-)

diff --git a/eval/eval.go b/eval/eval.go @@ -1,6 +1,7 @@ package eval import ( + "fmt" "interp/ast" "interp/object" ) @@ -19,6 +20,17 @@ func boolToObject(value bool) *object.Boolean { } } +func newError(format string, a ...interface{}) *object.Error { + return &object.Error{Message: fmt.Sprintf(format, a...)} +} + +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) { case *ast.Program: @@ -31,10 +43,19 @@ func Eval(node ast.Node) object.Object { return boolToObject(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.BlockStatement: return evalBlockStatement(node) @@ -42,6 +63,9 @@ func Eval(node ast.Node) object.Object { return evalIfExpression(node) case *ast.ReturnStatement: value := Eval(node.ReturnValue) + if isError(value) { + return value + } return &object.ReturnValue{Value: value} } return nil @@ -65,7 +89,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()) } } @@ -84,7 +108,7 @@ func evalShriekOperatorExpression(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 return &object.Integer{Value: -value} @@ -98,8 +122,10 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje return boolToObject(left == right) case operator == "!=": return boolToObject(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()) } } @@ -124,13 +150,15 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje case "!=": return boolToObject(leftValue != rightValue) 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 isTruthy(condition) { + if isError(condition) { + return condition + } else if isTruthy(condition) { return Eval(ie.Consequence) } else if ie.Alternative != nil { return Eval(ie.Alternative) @@ -156,8 +184,11 @@ func evalProgram(program *ast.Program) object.Object { var result 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 @@ -167,8 +198,11 @@ func evalBlockStatement(block *ast.BlockStatement) object.Object { var result 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 + } } } return result diff --git a/eval/eval_test.go b/eval/eval_test.go @@ -151,3 +151,27 @@ return 20;`, 10}, testIntegerObject(t, evaluated, tt.expected) } } + +func TestErrorHandling(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"5+true", "type mismatch: INTEGER + BOOLEAN"}, + {"5+true;5;", "type mismatch: INTEGER + BOOLEAN"}, + {"-true", "unknown operator: -BOOLEAN"}, + {"true+false;", "unknown operator: BOOLEAN + BOOLEAN"}, + {"if (10>1) { true + false; }", "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") + continue + } + if errObj.Message != tt.expected { + t.Errorf("wrong error message: %q instead of %q", errObj.Message, tt.expected) + } + } +} diff --git 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) Inspect() string { return rv.Value.Inspect() } func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } + +type Error struct { + Message string +} + +func (e *Error) Inspect() string { return "error: " + e.Message } +func (e *Error) Type() ObjectType { return ERROR_OBJ }