CS 214: Programming Languages
Spring 2009

Home|Syllabus|Schedule
<<|>>|ANTLR API|CITkit API|PolyD API|EasyMock API

Command-line Arguments
Interpreters, Iteration 18

This iteration required a change to the Hobbes front end. Make sure you have the latest.

This iteration required a change to CIAT. This has been updated on our lab machines; you'll have to update your gems on your own machines.

User Story

Programs that just compute what's hard coded in the program aren't particularly interested or useful. It'd be nice if you could run a Hobbes program from the command-line and provide it with some input.

User Story #18: Command-line arguments

Command-line arguments can be accessed using an argv array. argv[0] is the first command-line argument, argv[1] the second, etc. If an argument can be parsed as an integer, it should be; otherwise treat it as a string.

Example: argv[0] + argv[1]. When run with command-line arguments 3 and 5, the interpreter should return 8. When run with arguments foo and 99, it should return the string foo99.

The syntax for command-line arguments isn't an accident. They're supposed to look like array references. This way when you get to implementing arrays in all of their glory, you won't have to change any of acceptance tests; hopefully, it also reduces the changes that will need to happen internally.

So what will this change? The driver certainly changes since the command-line arguments go through it. (And keep in mind that "the driver" really refers to two things: the script/hobbes shell script and the HobbesCLI Java class.) It'll also require a change to the interpreter to handle the array, but this might not be as tricky as you might think.

Acceptance Tests

Write five CIAT tests for command-line arguments.

Some variations to consider:

Of course, this begs the question: how does one provide command-line arguments in a test? Add a command line element to the file:

adding a string and an integer from the command line
==== source
argv[0] + argv[1]
==== command line
foo 99
==== execution
foo99

You cannot specify multiple command lines in a CIAT file. If you want to try the same program with different inputs, you have to write multiple CIAT files.

Acceptance tests fail.

To the Interpreter!

If your unimplemented visitX() methods just return null, it's much harder to pinpoint problems. Have them throw a new IllegalArgumentException; then you'll know which method to implement next!

The acceptance tests should fail in the HobbesInterpreter, in the visitVariableETIR() method. Let's start testing it with a really simple test:

control.replay();
assertSame(null, myInterpreter.interpret(new VariableETIR(lvalue)));
control.verify();

control is an EasyMock control as before. lvalue is a mock object of type LValueTIR (created from control). The crappy-but-good-for-now assertion says that the visitVariableETIR() method should return null.

Add a shouldInterpreterLValue() method to HobbesInterpreterTest with the mock-object assertion from above. Define the necessary variables. Red bar.

Fix the visitVariableETIR() method. Green bar.

Replace null with a new mock-object variable result of type ExpressionTIR. Red bar.

So the question now is, what should be done with that lvalue? Just like the left and right subexpressions of an operator expression, this l-value expression needs to be evaluated (i.e., interpreted). However, your interpreter cannot interpret l-value expressions yet. Unlike the left and right subexpressions of an operator expression, an l-value expression is a different kind of expression: an l-value expression!

This means that l-value expressions have their own hierarchy and visitor. The quick-and-dirty solution now is to have HobbesInterpreter do double duty: interpret normal expressions (as an ExpressionTIRVisitor) and interpret l-value expressions (as an LValueTIRVisitor).

Let's get the code to force this, though. In terms of expectations, this is how you would state it in EasyMock:

EasyMock.expect(lvalue.accept(myInterpreter)).andReturn(result);

You expect that the lvalue will be interpreted my the interpreter under test, and that will return the result.

Add this code to shouldInterpreterLValue() before the replay. Compiler error!

The compiler should complain that myInterpreter is not an acceptable argument (pardon the pun).

Make HobbesInterpreter implement LValueTIRVisitor<ExpressionTIR> (in addition to any existing inheritance). Implement the missing methods (with "not implement" exceptions). Compiler should be happy. Red bar.

You'll get a complaint about the return value. The solution: use variableExpression.getLValue().accept(this) to get a value that can be returned.

Fix visitVariableETIR(). Green bar!

Victory is short lived since you've only deferred the computation.

Acceptance tests fail.

To the L-Value Interpreter!

Look at why the acceptance tests are failing. It's the visitSubscriptLValue() method that you just added. A "subscript l-value" consists of another l-value expression and a subscript. argv[0] is parsed into a "subscript l-value" with the argv as the recursive l-value expression and 0 as the subscript.

Based on the story for this iteration, you can assume a lot of things:

The first assumption means that you can safely ignore the l-value subexpression. The second assumption means that you can use a cast to bypass some evaluation. This makes evaluating a subscript l-value a base-case operation, like evaluating an integer or a string. And base cases are tested with real objects, not mock objects.

Create a shouldInterpretSubscriptExpression() method.

Here's an assertion to get you started:

assertEquals(
  new StringETIR("first command line argument"),
        new SubscriptLValueTIR(null, new IntegerETIR(0)).accept(myInterpreter)
        );

Add this assertion. Red bar.

The null is there just to boldly declare that the computation won't try to do anything with it. (You could replace it with new SimpleLValueTIR("argv") if you want to be more honest.) You can't use interpret(ExpressionTIR) because you're dealing with an l-value expression, so you need to use the accept(LValueTIRVisitor<T>) method provided by the CITkit library.

The expected value is rather bold. Where do I think this value is going to come from? Hard code it for now!

Have visitSubscriptLValue() return that expected value. Green bar!

Whenever testing without mocks, you have to write more than one assertion.

Write two more assertions for shouldInterpretSubscriptExpression(). This means first planning out the command-line arguments for testing. Red bar.

The assertion above establishes what the first command-line argument must be. At least one of the assertions should expect an IntegerETIR. At least one of the assertions should use an index greater than 3 (but keep in relatively small since you will have to hard code some data—5 seems like a good number to me).

And now you get to the main star of this iteration: the actual command-line arguments! Well, more accurately, you're going to fake them in the test for now.

Command-line arguments are another thing for the interpreter to remember (similar to the map of operator algorithms). So they have to be passed in as an argument to the HobbesInterpreter constructor.

Please name the parameter and instance variable appropriately.

Add an ExpressionTIR[] argument to the HobbesInterpreter constructor. Use that parameter to initialize a new instance variable.

This should give you two compiler errors.

The first place is in the command-line driver. For now, just give it an empty array (i.e., new ExpressionTIR[0]).

The second place is in the setup method HobbesInterpreterTest. You can create an array like this:

new ExpressionTIR[] {
  new StringETIR("first command line argument"),
  new IntegerETIR(42)
}

That array has only two elements of course. Yours should probably have at least six.

Use a hard-coded array of command-line arguments for the interpreter under test in HobbesInterpreterTest. Base it off the assertions in shouldInterpretSubscriptExpression(). Red bar.

It's red because you haven't generalized visitSubscriptLValue().

Use the command-line-arguments array in visitSubscriptLValue(). Use the index from the subscript l-value (with getIndex()); you'll have to cast it to IIntegerETIR and getValue(). Green bar.

Command-Line Arguments as TIRs

Ah, but those acceptance tests still complain!

Acceptance tests fail.

The complaint now should be an array index out of bounds. All of the tests that refer to command-line arguments are trying to get values out of an empty array since that's what you put into the main() method of the driver.

You want to use the arguments passed in args to main(String[] args) to build an array of ExpressionTIRs. It turns out do be relatively easy to do given the constraints of the story: turn into an integer if possible, otherwise a string. The IntegerETIR constructor handles the "turn into an integer if possible", and a caught NumberFormatException handles the "otherwise".

This comes from the Hobbes front end that I provide to you. The source code for the classes in that library are included in the JAR file. In Eclipse, use ctrl-click (cmd-click on Mac) on the CommandLineArguments in the code to navigate to its code.

But I did this work for you:

new CommandLineArguments(args).parse()

This will turn the arguments in args except for the first one (which is the name of the Hobbes source file) into ExpressionTIRs.

Use this expression instead of the empty array of ExpressionTIRs. Green bars all around!

Changing the Real Driver

The driver you can run from the command line is actually in scripts/hobbes. Presently it won't work with command-line arguments.

Write a command-line argument Hobbes program in hobbes (maybe one that uses <).

Try running it:

unix-%  ./scripts/hobbes hobbes/less_than.hob 5 10

You'll get an error about an index out of bounds.

In script/hobbes, change the $1 in the last line to "$@".

$@ is the shell-script variable for "all command-line variables". Putting it in double quotes will quote the values individually, preserving whitespace you might put in originally. So you can do things like this:

unix-%  ./scripts/hobbes hobbes/string_comparison.hob foo xyz abc
true
unix-%  ./scripts/hobbes hobbes/string_comparison.hob 'foo xyz' abc
false

Use Hobbes programs like argv[0] and argv[1] to figure out how the quotes affect things.