commit 073b20492e0f7bb8c9b8e93b013a8ac68e66cd6d
parent d879cb5ebc90f1eb8925311f0695c77534f69c10
Author: bsandro <email@bsandro.tech>
Date: Fri, 24 Jun 2022 01:51:14 +0300
eval error handing
Diffstat:
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 }