Skip to content

Commit d9376e1

Browse files
committed
3.6 Evaluation (conditionals)
Implement support for evaluating if-else-expressions.
1 parent c9cb6f0 commit d9376e1

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

evaluator/evaluator.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ func Eval(node ast.Node) object.Object {
3535
// Traverse the tree and evaluate every statement of the *ast.Program.
3636
return evalStatements(node.Statements)
3737

38+
case *ast.BlockStatement:
39+
return evalStatements(node.Statements)
40+
3841
case *ast.ExpressionStatement:
3942
// If the statement is an *ast.ExpressionStatement we evaluate its
4043
// expression. An expression statement (not a return statement and not
@@ -58,6 +61,9 @@ func Eval(node ast.Node) object.Object {
5861
left := Eval(node.Left)
5962
right := Eval(node.Right)
6063
return evalInfixExpression(node.Operator, left, right)
64+
65+
case *ast.IfExpression:
66+
return evalIfExpression(node)
6167
}
6268

6369
return nil
@@ -181,3 +187,30 @@ func evalIntegerInfixExpression(
181187
return NULL
182188
}
183189
}
190+
191+
func evalIfExpression(ie *ast.IfExpression) object.Object {
192+
// Deciding what to evaluate.
193+
194+
condition := Eval(ie.Condition)
195+
196+
if isTruthy(condition) {
197+
return Eval(ie.Consequence)
198+
} else if ie.Alternative != nil {
199+
return Eval(ie.Alternative)
200+
} else {
201+
return NULL
202+
}
203+
}
204+
205+
func isTruthy(obj object.Object) bool {
206+
switch obj {
207+
case NULL:
208+
return false
209+
case TRUE:
210+
return true
211+
case FALSE:
212+
return false
213+
default:
214+
return true
215+
}
216+
}

evaluator/evaluator_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,38 @@ func TestBangOperator(t *testing.T) {
100100
}
101101
}
102102

103+
func TestIfElseExpressions(t *testing.T) {
104+
// The consequence part of the conditional will be evaluated when the
105+
// condition is "truthy”. And "truthy” means: it’s not null and it’s not
106+
// false. It doesn’t necessarily need to be true.
107+
108+
tests := []struct {
109+
input string
110+
expected interface{}
111+
}{
112+
{"if (true) { 10 }", 10},
113+
{"if (false) { 10 }", nil},
114+
{"if (1) { 10 }", 10},
115+
{"if (1 < 2) { 10 }", 10},
116+
{"if (1 > 2) { 10 }", nil},
117+
{"if (1 > 2) { 10 } else { 20 }", 20},
118+
{"if (1 < 2) { 10 } else { 20 }", 10},
119+
}
120+
121+
for _, tt := range tests {
122+
evaluated := testEval(tt.input)
123+
// Type assertion and conversion to allow nil in our expected field.
124+
integer, ok := tt.expected.(int)
125+
if ok {
126+
testIntegerObject(t, evaluated, int64(integer))
127+
} else {
128+
// When a conditional doesn’t evaluate to a value it's supposed to
129+
// return NULL.
130+
testNullObject(t, evaluated)
131+
}
132+
}
133+
}
134+
103135
func testEval(input string) object.Object {
104136
l := lexer.New(input)
105137
p := parser.New(l)
@@ -136,3 +168,11 @@ func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool {
136168
}
137169
return true
138170
}
171+
172+
func testNullObject(t *testing.T, obj object.Object) bool {
173+
if obj != NULL {
174+
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)
175+
return false
176+
}
177+
return true
178+
}

0 commit comments

Comments
 (0)