String (and Integers)
Interpreters, Iteration 4
User Story
User Story #4: Interpreter evaluates a string.
A Hobbes program can be a single string in double quotes. The interpreter prints the value of the string without the quotes.
Examples:
"This is a string"
and
"So is this!!!"
They evaluate to This is a string
and So it
this!!!
, respectively.
You need to do this in addition to interpreting an integer.
The procedural solution is a bit tricky because input strings
will be written like "hello"
, complete with the double
quotes. The ASTs that you get back from the parser will still have
the double quotes, and you'll have to handle them. (The
object-oriented solution uses the TIR builder, a step beyond the
parser, and the TIR builder takes off the double quotes for
you.)
The object-oriented solution is easy. You'll change one word in your existing code (and write a couple of tests). Don't worry: the object-oriented solution will be more interesting in the next iteration.
Procedural Solution
Compare and contrast with the existing createIntegerAST(String).
Your procedural solution receives an AST, and it's supposed to
return a String
result. You can easily build
STRING
ASTs:
Create a new method createStringAST(String)
which
returns a new ANTLR AST of type
HobbesParser.STRING
.
This will allow you to easily create assertions for
STRING
ASTs.
Create a new test method shouldInterpretStrings()
.
Compile, and green bar.
You can't written any assertions yet, so the code should still green bar.
Here's an assertion:
assertEquals("abc123", myInterpreter.interpret(createStringAST("\"abc123\"")));
Note the double quotes around the actual string. Your interpreter has to strip these double quotes away.
Add this assertion to the test method, write two more, compile, and red bar.
You should get a complaint about the double quotes. What you
already have for the interpret(Tree)
method should
return something for a STRING
, except for those double
quotes.
So turn your attention to
ProceduralInterpreter#interpret(Tree)
. Just returning
the text of the AST is correct for an
INTEGER
, and you do not want to lose this.
However, you have to do something different for a
STRING
.
This is an integer value being compared for
equality. This is why C included the
switch
statement. It doesn't explain why Java has it,
except for procedural demonstrations like this.
You need to make a decision based on the type of the AST. With a
procedural approach, the right way to do this is to get the AST's
type from the AST itself. Not too surprisingly, ANTLR gives us a
getType()
method for Tree
s. This returns
an int
which can be compared against constants that
ANTLR defined in the grammar: HobbesParser.INTEGER
and
HobbesParser.STRING
.
So I could write code like this if I just needed some feedback:
switch (tree.getType()) { case HobbesParser.INTEGER: return "this is an integer"; case HobbesParser.STRING: return "this is a string"; default: throw new IllegalArgumentException(tree.getType() + " is unrecognized"); }
Based on the type of the AST (in tree
, of type
Tree
), I decide which feedback message to return. If I
need to do more work for one or the other, I just add more code for
that case. The throw
clause is helpful for debugging
purposes. (Hopefully, you never need it, but if it ever gets
triggered, it can save you a lot of time debugging.)
Use a switch
statement in
interpret(Tree)
to decide between INTEGER
and STRING
as in the code above. Use the old body of
the method for the INTEGER
case. Do not write
anything for the STRING
case yet. Do
include a default
case as above. Compile, and
different red bar.
You should get the thrown exception causing the red bar. (The old integer tests should still pass!) If you look at the stack trace in the exception report from JUnit, you should be able to track the problem from the specific assertion that failed all the way back to the specific line of code that threw the exception. So you know where to fix the problem!
Add a case for STRING
. Start with just returning
the text from the tree unchanged. Compile, and original red
bar.
The fix is to do some string manipulation. The
String
class has methods length()
and
substring(start, end)
which you'll find handy. It
won't be pretty.
Complete the STRING
case. Compile, and green
bar.
Try your procedural driver!
Write a couple Hobbes programs, and run the procedural driver over them.
Object-Oriented Solution
The object-oriented solution turns out to be really easy.
@Test public void shouldInterpretStrings() { assertEquals("foo", myInterpreter.interpret(new StringETIR("foo"))); }
Add this test method to OOHobbesInterpterTest
.
Compile fails.
The compile fails because
OOHobbesInterpreter#interpret(IntegerETIR)
takes an
IntegerETIR
as an argument. You're giving it a
StringETIR
here. This being an object-oriented
solution, you should imagine that the solution involves
inheritance. Both IntegerETIR
and
StringETIR
implement an interface named
ExpressionTIR
.
Change interpret(IntegerETIR)
to be
interpret(ExpressionTIR)
. Compile, and green bar!
It turns out, in this case, that you could go as high as
Object
for this parameter's type because the only
method you're calling is Object#toString()
. And that
method has been overridden in the ways you want for both
IntegerETIR
and StringETIR
.
Add two more assertions to
shouldInterpretStrings()
. Compile, and green bar.
Run your object-oriented driver over string programs, too.
At first, the driver will fail, but you can fix this by removing
the (IntegerETIR)
cast.