Skip to content

Commit

Permalink
3.6 Evaluation (conditionals)
Browse files Browse the repository at this point in the history
Implement support for evaluating if-else-expressions.
  • Loading branch information
cedrickchee committed Mar 30, 2020
1 parent c9cb6f0 commit d9376e1
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 0 deletions.
33 changes: 33 additions & 0 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func Eval(node ast.Node) object.Object {
// Traverse the tree and evaluate every statement of the *ast.Program.
return evalStatements(node.Statements)

case *ast.BlockStatement:
return evalStatements(node.Statements)

case *ast.ExpressionStatement:
// If the statement is an *ast.ExpressionStatement we evaluate its
// expression. An expression statement (not a return statement and not
Expand All @@ -58,6 +61,9 @@ 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)
}

return nil
Expand Down Expand Up @@ -181,3 +187,30 @@ func evalIntegerInfixExpression(
return NULL
}
}

func evalIfExpression(ie *ast.IfExpression) object.Object {
// Deciding what to evaluate.

condition := Eval(ie.Condition)

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

func isTruthy(obj object.Object) bool {
switch obj {
case NULL:
return false
case TRUE:
return true
case FALSE:
return false
default:
return true
}
}
40 changes: 40 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,38 @@ func TestBangOperator(t *testing.T) {
}
}

func TestIfElseExpressions(t *testing.T) {
// The consequence part of the conditional will be evaluated when the
// condition is "truthy”. And "truthy” means: it’s not null and it’s not
// false. It doesn’t necessarily need to be true.

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 {
evaluated := testEval(tt.input)
// Type assertion and conversion to allow nil in our expected field.
integer, ok := tt.expected.(int)
if ok {
testIntegerObject(t, evaluated, int64(integer))
} else {
// When a conditional doesn’t evaluate to a value it's supposed to
// return NULL.
testNullObject(t, evaluated)
}
}
}

func testEval(input string) object.Object {
l := lexer.New(input)
p := parser.New(l)
Expand Down Expand Up @@ -136,3 +168,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
}

0 comments on commit d9376e1

Please sign in to comment.