Skip to content

Commit

Permalink
feat(evaluator): add error handling to evaluator
Browse files Browse the repository at this point in the history
throws error where necessary.
  • Loading branch information
ahmedsaheed committed Mar 4, 2024
1 parent 202b0f9 commit 87711c9
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 12 deletions.
63 changes: 51 additions & 12 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package evaluator
import (
"esolang/lang-esolang/ast"
"esolang/lang-esolang/object"
"fmt"
)

var (
Expand All @@ -18,30 +19,43 @@ The eval typically receives an *ast.Program and recursively traverses whilst ev
func Eval(node ast.Node) object.Object {
// 1. Check the type of the node
switch node := node.(type) {
// 2. If the node is a *ast.Program, evaluate the statements

// 2. If the node is a *ast.Program, evaluate the statements
case *ast.Program:
return evalProgram(node)
// 3. If the node is a *ast.ExpressionStatement, recursively evaluate the expression

// 3. If the node is a *ast.ExpressionStatement, recursively evaluate the expression
case *ast.ExpressionStatement:
return Eval(node.Expression)
// Work with expressions

// Work with expressions
case *ast.IntegerLiteral:
return &object.Integer{Value: node.Value}

case *ast.Boolean:
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.BlockStatement:
return evalBlockStatement(node)

case *ast.IfExpression:
return evalIfExpression(node)

case *ast.ReturnStatement:
value := Eval(node.ReturnValue)
if isError(value) {return value}
return &object.ReturnValue{Value: value}
}

Expand All @@ -50,7 +64,7 @@ func Eval(node ast.Node) object.Object {

func evalIfExpression(ifExpressionNode *ast.IfExpression) object.Object {
condition := Eval(ifExpressionNode.Condition)

if isError(condition) {return condition}
if isTruthy(condition) {
return Eval(ifExpressionNode.Consequence)
} else if ifExpressionNode.Alternative != nil {
Expand Down Expand Up @@ -79,8 +93,10 @@ func evalInfixExpression(operator string, leftOperand, rightOperand object.Objec
return nativeBoolToBooleanObject(leftOperand == rightOperand)
case operator == "!=":
return nativeBoolToBooleanObject(leftOperand != rightOperand)
case leftOperand.Type() != rightOperand.Type():
return newError("type mismatch: %s %s %s", leftOperand.Type(), operator, rightOperand.Type())
default:
return NULL
return newError("unknown operator: %s %s %s", leftOperand.Type(), operator, rightOperand.Type())
}
}

Expand All @@ -107,7 +123,7 @@ func evalIntegerInfixExpression(operator string, leftOperand, rightOperand objec
case "!=":
return nativeBoolToBooleanObject(leftValue != rightValue)
default:
return NULL
return newError("unknown operator: %s %s %s", leftOperand.Type(), operator, rightOperand.Type())
}
}

Expand All @@ -118,11 +134,15 @@ 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())
}
}
// evalMinusPrefixOperatorExpression evaluates the right object and returns a new object with the value negated
func evalMinusPrefixOperatorExpression(right object.Object) object.Object {

if right.Type() != object.INTEGER_OBJ {
return newError("unknown operator: -%s", right.Type())
}
// if operand ins't integer - we escape
if right.Type() != object.INTEGER_OBJ {
return NULL
Expand Down Expand Up @@ -166,12 +186,19 @@ func evalStatements(statements []ast.Statement) object.Object {
}


func newError(format string, a ...interface{}) *object.Error {
return &object.Error{Message: fmt.Sprintf(format, a...)}
}

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 {
if result != nil {
resultType := result.Type()
if resultType == object.RETURN_VALUE_OBJ || resultType == object.ERROR_OBJ {
return result
}
}
}
return result
Expand All @@ -181,10 +208,22 @@ func evalProgram(program *ast.Program) object.Object {
var evaluatedResult object.Object
for _, statement := range program.Statements {
evaluatedResult = Eval(statement)

if returnValue, ok := evaluatedResult.(*object.ReturnValue); ok {
return returnValue.Value
}
switch evaluatedResult := evaluatedResult.(type) {
case *object.ReturnValue:
return evaluatedResult.Value
case *object.Error:
return evaluatedResult
}
// if returnValue, ok := evaluatedResult.(*object.ReturnValue); ok {
// return returnValue.Value
// }
}
return evaluatedResult
}

func isError(obj object.Object) bool {
if obj != nil {
return obj.Type() == object.ERROR_OBJ
}
return false
}
37 changes: 37 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,42 @@ func TestBangOperator(t *testing.T) {
}
}


func TestErrorHandling(t *testing.T) {
tests := []struct{
input string
expectedMessage string
}{
{"10 + true;", "type mismatch: INTEGER + BOOLEAN"},
{"10 + true; 10;", "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 _, test := range tests {
evaluated := testEval(test.input)
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("no error object returned. got=%T(%+v)", evaluated, evaluated)
continue
}
if errObj.Message != test.expectedMessage {
t.Errorf("wrong error message. expected=%q, got=%q", test.expectedMessage, errObj.Message)
}
}
}

func testBooleanObject(t *testing.T, evaluated object.Object, b bool) bool {
boolean, ok := evaluated.(*object.Boolean)
if !ok {
Expand Down Expand Up @@ -179,6 +215,7 @@ func testNullObject(t *testing.T, evaluated object.Object) bool {
}
return true
}

func testEval(input string) object.Object {
lexer := lexer.New(input)
parser := parser.New(lexer)
Expand Down
9 changes: 9 additions & 0 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
BOOLEAN_OBJ = "BOOLEAN"
NULL_OBJ = "NULL"
RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR"
)

type Object interface {
Expand Down Expand Up @@ -44,3 +45,11 @@ 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 }

0 comments on commit 87711c9

Please sign in to comment.