Skip to content

Commit

Permalink
Add conditional support to evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
dcrodman committed May 9, 2020
1 parent bdb044f commit 2d1fe3e
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 12 deletions.
51 changes: 49 additions & 2 deletions evaluator/evaluator.go
Expand Up @@ -15,7 +15,7 @@ var (
func Eval(node ast.Node) object.Object {
switch node := node.(type) {
case *ast.AST:
return evalStatements(node.Statements)
return evalProgram(node.Statements)
case ast.ExpressionStatement:
return Eval(node.Expression)
case *ast.Integer:
Expand All @@ -29,15 +29,26 @@ func Eval(node ast.Node) object.Object {
left := Eval(node.Left)
right := Eval(node.Right)
return evalInfixExpression(node.Operator, left, right)
case *ast.IfExpression:
return evalIfExpression(node)
case *ast.BlockStatement:
return evalBlockStatement(node)
case *ast.ReturnStatement:
val := Eval(node.Value)
return &object.Return{Value: val}
}
return nil
}

func evalStatements(stmts []ast.Statement) object.Object {
func evalProgram(stmts []ast.Statement) object.Object {
var result object.Object

for _, stmt := range stmts {
result = Eval(stmt)

if returnValue, ok := result.(*object.Return); ok {
return returnValue.Value
}
}

return result
Expand Down Expand Up @@ -123,6 +134,42 @@ func evalIntegerInfixExpression(
}
}

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

if isTruthy(condition) {
return Eval(ie.Consequence)
} else if ie.Alternative != nil {
return Eval(ie.Alternative)
}
return NULL
}

func isTruthy(obj object.Object) bool {
switch obj {
case NULL, FALSE:
return false
case TRUE:
return true
default:
return true
}
}

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
}
}

return result
}

func intToIntegerObject(val int64) *object.Integer {
return &object.Integer{Value: val}
}
Expand Down
64 changes: 60 additions & 4 deletions evaluator/evaluator_test.go
Expand Up @@ -7,6 +7,12 @@ import (
"testing"
)

func testEval(input string) object.Object {
p := parser.New(lexer.New(input))
program := p.ParseProgram()
return Eval(program)
}

func TestEvalIntegerExpression(t *testing.T) {
tests := []struct {
input string
Expand Down Expand Up @@ -88,10 +94,52 @@ func TestBangOperator(t *testing.T) {
}
}

func testEval(input string) object.Object {
p := parser.New(lexer.New(input))
program := p.ParseProgram()
return Eval(program)
func TestIfElseExpressions(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{"if (true) { 10 }", 10},
{"if (false) { 10 }", nil},
{"if (1) { 10 }", 10},
{"if (1 < 2) { 10 }", 10},
{"if (1 > 2) { 10 }", nil},
{"if (1 > 2) { 10 } else { 20 }", 20},
{"if (1 < 2) { 10 } else { 20 }", 10},
}

for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)

if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
testNullObject(t, evaluated)
}
})

}
}

func TestReturnStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"return 10;", 10},
{"return 10; 9;", 10},
{"return 2 * 5; 9;", 10},
{"9; return 2 * 5; 9;", 10},
}

for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
evaluated := testEval(tt.input)
testIntegerObject(t, evaluated, tt.expected)
})
}
}

func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool {
Expand Down Expand Up @@ -125,3 +173,11 @@ func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {

return true
}

func testNullObject(t *testing.T, obj object.Object) bool {
if obj != NULL {
t.Errorf("object is not NULL, got=%T (%+v)", obj, obj)
return false
}
return true
}
23 changes: 17 additions & 6 deletions object/object.go
Expand Up @@ -8,6 +8,8 @@ const (
INTEGER_OBJ = "integer"
BOOLEAN_OBJ = "boolean"
NULL_OBJ = "null"

RETURN_VALUE_OBJ = "RETURN_VALUE"
)

type Object interface {
Expand All @@ -19,17 +21,26 @@ type Integer struct {
Value int64
}

func (i *Integer) Type() ObjectType { return INTEGER_OBJ }
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }
func (*Integer) Type() ObjectType { return INTEGER_OBJ }
func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) }

type Boolean struct {
Value bool
}

func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ }
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }
func (*Boolean) Type() ObjectType { return BOOLEAN_OBJ }
func (b *Boolean) Inspect() string { return fmt.Sprintf("%t", b.Value) }

type Null struct{}

func (n *Null) Type() ObjectType { return NULL_OBJ }
func (b *Null) Inspect() string { return "null" }
func (*Null) Type() ObjectType { return NULL_OBJ }
func (*Null) Inspect() string { return "null" }

type Return struct {
Value Object
}

func (*Return) Type() ObjectType { return RETURN_VALUE_OBJ }
func (r *Return) Inspect() string {
return fmt.Sprintf("return %s", r.Value.Inspect())
}

0 comments on commit 2d1fe3e

Please sign in to comment.