If Expressions
Interpreters, Iteration 17
This iteration required a change to the Hobbes front end. Make sure you have the latest.
User Story
if
expressions!
User Story #17: Interpreting an if
expression
An if
expression is made from this pattern:
if expr then expr else expr end
where if
, then
, else
, and
end
are keywords and the expr
s
are valid expressions (including other if
expressions).
If the test expression evaluates to true, interpret the then expression. If the test expression evaluate to false, interpret the else expression. If the test expression evaluates to any other value (integer or string), generate an error.
if
expression cannot be parenthesized, but there's
no need to (except for readability, perhaps). Why not?
Acceptance Tests
Write some acceptance tests for this user story.
Consider these issues:
- The test value evaluates to true. The test value evaluates to false.
- Complicated subexpressions.
- Nesting
if
expressions in all three subexpressions.
Do not worry about the error case.
Also, there's no reason why your Hobbes programs need to be on just one line. The front end deals with multi-line programs!
And here's an interesting expression:
if 1 == 1 then 20 else "20" end + 50
Tweak the comparison, and the meaning of +
changes!
If True
Similar to the operator expressions, an if expression has subexpressions. And again, you don't want to have to test all the different variations of those subexpressions (like you've already done for the acceptance tests). Instead, you want to mock out the subexpressions.
But, first, consider what you need to test. An if
expression can be executed one of two ways: the condition is true,
and the then expression is evaluated and returned. Or: the
condition is false, and the else expression is evaluated and
returned.
Let's start with the first path through an if
expression.
Write a new test method
shouldInterpretIfWithTrueCondition()
in
HobbesInterpreterTest
.
Identify what's under test: the interpreter itself (as it is in
all tests in HobbesInterpreterTest
) and an
IfETIR
. You already have a real interpreter in an
instance variable, so you need a real IfETIR
.
si
is Latin for "if"; els
is a shorted
version of "else".
Why?
IfETIR si = new IfETIR(condition, then, els);
Make this declaration.
Since the subexpressions aren't under test, you're free to mock them out. And when you're using mock objects, you should mock every chance you get!
Declare an EasyMock control (as you did for operator expression testing).
Declare and initialize condition
,
then
, and els
as
ExpressionTIR
mock objects (using the control).
Let's jump to the end:
Write replay, assertion, and verify statements at the end of the test method. Declare and initialize any new mocks.
Remember that assertions with mock objects should use
assertSame()
:
assertSame(result, myInterpreter.interpret(si));
And, yes, result
is a new mock object.
Your code should compile and...
Red bar.
The complaint should be the exception from
visitIfETIR()
in HobbesInterpreter
.
Presently, there's nothing you could write there that
would get the test to pass. (Can you see why?)
Of course, you're not done with the test. Go back to what you want to happen:
- The condition is interpreted, and true is returned.
- The then expression is interpreted, and
result
is returned.
These can both be expressed as EasyMock expectations. And you've
already seen how to set an expectation for a subexpression to be
interpreted, back in the operator expression test. Using CITkit,
"true" is represented by BooleanETIR.TRUE
.
Write the two expectations. Red bar.
You're closer.
Write code for visitIfETIR()
that does not
use an if
. Green bar.
All that work, and I tell you not to use an
if
statement in Java? What gives? Well, as your green
bar demonstrates, you don't need one.
Yet!
If False
Write a new test method
shouldInterpretIfWithFalseCondition()
.
Write a test for this just as you did for
shouldInterpretIfWithTrueCondition()
. Red bar. Fix the
computation code. Green bar.
Yes, you will need an if
now. Due to the
way that booleans are created with my Hobbes front end and CITkit,
you can test the result of the condition ==
BooleanETIR.TRUE
and == BooleanETIR.FALSE
.
That exercise was to demonstrate thinking about all sides of a computation. Your tests are supposed to lead you to the right code, and if it doesn't, you need to tweak an existing test or write a new one.
There was no tweaking to be done here. An if
expression in Hobbes requires Java code that executes differently
based on the result of the condition. The only way to do this is
with two (or more) assertions. With mock objects, the two paths
require two tests.
Acceptance tests should green bar.
Cheating?
Does it seem like you're cheating? using a Java if
to implement a Hobbes if
? That's what an interpreter
is all about. If you squint hard enough, it's even what a compiler
does: use an i386 or PowerPC "if" to implement a C++
if
.
It's not cheating because you are adding something. Hobbes uses a different syntax from Java which you might prefer, although this is really a property of the front end.
You also get to make semantic choices as well: should you really
allow mixed arithmetic (e.g., "20" + 50)? should 0
be
considered "false"? what if 666
were the only
"false" value? You could tune your interpreter so that it recorded
how many times the different operators were evaluated.