| CIT
594 Assignment 7: Interpreter, Part I Spring 2006, David Matuszek |
In this assignment we will implement an interpreter for the mini-language Logo2006, which is based loosely on an ancient (1966) programming language designed for children. The idea is that you write a program to instruct a "turtle" how to walk around the screen; the turtle has colored pens which it drags as it walks, so you can see where it has been. The object is to "teach" (program) the turtle to draw various objects: squares, spirals, stars, houses, faces, etc. All the most important features of a programming language are included.
Wikipedia has a good article on Logo.
In this assignment we will not implement procedures or the call
statement; that's for Part II.
Your Tokenizer takes a String input and produces Tokens,
each of which has a Type and a String value. The Tokens were used
in your Recognizer, which used recursive descent to recognize
Strings according to a particular grammar.
This Recognizer is itself no longer needed, but it formed the basis of a Parser
class which recognizes Strings and converts them into Trees. Now,
what shall we do with those Trees...?
To finish implementing Logo2006, we need to interpret a Tree (called a parse tree or an abstract syntax tree) that represents a Logo2006 program. This means moving a cursor (traditionally called a turtle) around on a canvas, according to commands issued by an interpreter. All of this will take place within the context of a GUI.
public class Logo- The only purpose of this class is to give us a well-named starting point. Here is the class in its entirety (well, you should add some Javadoc to it):
public class Logo {
public static void main(String[] args) {
LogoGui.main(null);
}
}
class LogoGui- Provides facilities for creating, loading, editing, and saving files, controlling the
Interpreter, and controlling the speed of the turtle. I have supplied this as a skeleton class that does all the routine stuff; you need to modify it to interact with theInterpreter.
class Interpreter- Executes (interprets) the Logo2006 program shown in the
LogoGui. (Note: If the GUI is displaying the syntax tree instead of the program, theInterpretershould still execute the program.) In order to facilitate grading with our JUnit tests, I will specify some of theInterpretermethods below.
class Turtle- The turtle converts calls from the
InterpreterintoTurtleCommands, which it passes along to theTurtleCanvas. It also keeps track of its own state (what coordinates it is at, what direction it is facing, whether its pen is up or down, what color it is using, its speed, and whether it is paused). In order to facilitate grading with our JUnit tests, I will specify some of theTurtlemethods below.
interface TurtleCommand- Declares the method
void execute(Graphics g).
class ColorCommand implements TurtleCommand
class DrawLineCommand implements TurtleCommand
class MoveCommand implements TurtleCommand
class HomeCommand implements TurtleCommand- Each of these holds information specific to that command. These are very small classes: a
DrawLineCommandhas fieldsx1,y1,x2,y2to hold the endpoints of the line, and itsexecutemethod calls, while a g.drawLine(x1, y1, x2, y2)HomeCommandhas no fields and itsexecutemethod does nothing.
class TurtleCanvas extends JPanel- This is the drawing area. It maintains a list of all
TurtleCommands that it has been given. It requires the method; when called, this method will execute all those commands, in order, and afterwards it will draw the turtle (it may have to ask thepublic void paint(Graphics g) Turtlewhere it is and what direction it is facing). It will have aclear()command to empty the list ofTurtleCommands (used when running a new program). This class does not need JUnit tests.
Ideally, all classes are independent, and can be tested independently. The above classes fall short of this ideal. To make it feasible for us to test your classes, therefore,
LogoGui creates the Interpreter.Interpreter constructor creates the LogoGui
and the Turtle.
LogoGui creates the TurtleCanvas (in a
method called from the constructor)Interpreter calls the getCanvas() method
(which you write) of the LogoGui to get the TurtleCanvas,
and uses this as an argument to the Turtle constructor.Is this the best way of doing things? Probably not. But it does work.
One of the central principles of JUnit testing is that testing should be done frequently, hence must require little effort. You shouldn't have to look at anything more than the green bar to know that all your tests passed. Since testing graphics requires more than this, the goal is to do as much testing as possible without graphics.
The two classes that require testing are Interpreter and Turtle.
You can test expressions without reference to anything else (as long as you use numeric constants, not variables). You can test conditions and directions once you have expressions working (conditions return a true/false value; directions return an integer number of degrees).
Before doing much else, you must be able to store and fetch the
values of variables, so write methods to test these next. (Because "store"
and "fetch" are not Logo2006 commands, you probably should make these
methods protected.) Until you implement procedures, all your variables
will be global.
Once you have global variables working, you can test out assignment statements, repeat loops, while loops, and if statements. Test these by doing some computation, saving the result in a variable, and testing the (numerical) result.
You can test define and call statements by writing some Logo2006 procedures that do computation, and storing the results in global variables (local variables no longer exist once the procedure finishes). You can test local variables by writing procedures that use them (preferably, procedures that use other procedures), and storing the results in global variables. Recursion is more difficult to test, because our procedures don't return values, but it can be done.
Commands that tell the turtle to do something (move, pendown,
etc.) should be tested in a separate test class.
The turtle has state: where it is, whether the pen is up or down, etc.
You can test turtle commands by issuing the command (such as move),
then using getter methods to test if the turtle's state is as expected (such
as, is it in the correct x, y position after a move). You can speed
these tests up by setting the turtle's speed to maximum.
Ultimately, of course, you have to look at the results; but you shouldn't have to do that very often.
I need to specify signatures so that we can do JUnit testing. Here they are, with little or no explanation; we will test these methods. If the purpose of a method is not clear, ask.
Interpreter
protected Interpreter() // constructorprotected int evaluateExpression(Tree<Token> node)protected boolean evaluateCondition(Tree<Token> node)protected int evaluateDirection(Tree<Token> node)protected void store(String name, int value)protected int fetch(String name)protected void interpret(Tree<Token> node)protected void stripProcedures(Tree<Token> tree) // for Part IIprotected void callProcedure(String name, Tree<Token> actualParameters)
protected void callProcedure(TreecallStatement) // for Part II Turtle
protected Turtle(TurtleCanvas canvas) // constructorprotected void color(String colorName)protected void face(int degrees)protected void home()protected void move(int distance)protected void pendown()protected void penup()public void setPaused(boolean paused)protected void setSpeed(int speed)
Getters:
protected double getX() // turtle's distance in pixels east of homeprotected double getY()// turtle's distance in pixels south of homeprotected int facing() // degrees counterclockwise from eastprotected int getSpeed()protected Color getColor()protected boolean isPenDown()protected boolean isPaused()
This is the same table as in the Parser assignment, but I have replaced the Comments column with a How To Interpret column. I am making a distinction between "evaluate," which says to compute a value, and "interpret," which does not.
| Grammar rule (partial) | Example input |
What it should push onto the stack
|
How To Interpret |
|---|---|---|---|
<variable> ::= <NAME> |
"foo" |
|
Depends on context. In an <expression>,
look up the value of the variable. In an assignment statement, assign a
value to the variable. See below. |
<command> ::= |
"move 5 \n" |
![]() |
Evaluate the <expression> and tell the
Turtle to move forward that many spaces. If the pen is down, the turtle
draws; otherwise, it moves without drawing. |
<command> ::= |
"turn east \n" |
![]() |
Evaluate <direction> to find the direction
(in degrees counterclockwise from East) and tell the Turtle to face in that
direction. |
<command> ::= |
|
![]() |
Evaluate <direction> and add it to the
direction the turtle is currently facing. Tell the Turtle to face in that
new direction. |
<command> ::= |
"penup \n" |
|
Tell the Turtle not to leave a line behind it when it moves. |
<command> ::= |
"penup \n" |
|
Tell the Turtle to leave a line behind it (in the current color) when it moves. |
<command> ::= |
"color red \n" |
![]() |
Tell the Turtle what color to use in any future drawing. |
<command> ::= |
"home \n" |
|
Tell the Turtle to move to the center of the TurtleCanvas and face East. |
<command> ::= |
"x = 5 \n" |
![]() |
Evaluate the right child <expression> and save the
numerical result in the left child <variable>. See below. |
<command> ::= |
|
![]() |
Evaluate the <expression>, then interpret the <block>
that many times. |
<command> ::= |
|
![]() |
Evaluate the <condition>, and if it is true, interpret
the <block>. Repeat until the <condition>
becomes false. |
<command> ::= |
|
![]() |
Evaluate the <condition>, and if it is true, interpret
the <block>, otherwise do nothing. |
<command> ::= |
|
![]() |
Evaluate the <condition>, and if it is true, interpret
the first <block>, else interpret the second <block>. |
<command> ::= |
"call foo 1 2 3\n" |
![]() |
Don't do anything. We'll handle this in a followup assignment. |
<colorName> ::= "red"
|
"red" |
|
Tell the Turtle to set the current color. |
<block> ::= "["
<eol> |
|
![]() |
Interpret each of the children of the |
<comparator> ::= "<"
| "="
| ">" |
"<" |
|
See <comparison>. |
<comparison> ::= <expression> <comparator> <expression> |
"x < 5" |
![]() |
Evaluate the two children, compare the results, and return a true or false value. |
<condition> ::= <disjunct> { "or"
<disjunct> } |
"x<0 or x>100" |
![]() |
Evaluate the first child, and if it is true, return true. If the first child is false, evaluate the second child, and return that result. |
<condition> ::= <disjunct> { "or"
<disjunct> } |
"x<0 or x>y and y>100" |
![]() |
As above. |
<disjunct> ::= <conjunct> { "and"
<conjunct> } |
"x>y and y>100" |
![]() |
Evaluate the first child, and if it is false, return false. If the first child is true, evaluate the second child, and return that result. |
<disjunct> ::= <conjunct> { "and"
<conjunct> } |
"x<y and y<z and z<100" |
![]() |
As above. |
<conjunct> ::= [ "not"
] <logicalFactor> |
"not x < 5" |
![]() |
Evaluate the child and return its negation. |
<logicalFactor> := <comparison> |
"x < 5" |
![]() |
This is the same Tree as for a <comparison>. See <comparison>. |
<expression> ::= <term> |
"foo + 5" |
![]() |
Evaluate the two children and return their sum. |
<expression> ::= <term> |
"x + y + z" |
|
Evaluate the two children and return their sum. |
<term> ::= <factor> |
"foo * 5" |
![]() |
Evaluate the two children and return their product. |
<factor> ::= <name> |
"foo" |
|
When a variable occurs in an <expression>, look up
its value in a hash table, and return that value. |
<factor> ::= "("
<expression> ")" |
"(foo + 5)" |
![]() |
Evaluate the two children and return their sum. |
<procedure> ::= "define"
<name> |
|
![]() |
Don't do anything. We'll handle this in a followup assignment. |
<program> ::= <command> { <command> } |
"penup \n |
![]() |
Interpret the |
<eol> ::= "\n" |
"\n" |
|
No tree, nothing to do. |
The Interpreter walks the parse tree and interprets the commands
as necessary. Many of these will be control commands (repeat, if,
while, call, and assignment statements). These commands
are handled directly by the Interpreter class.
Some commands (move, turn, penup, pendown,
color, home) will cause the turtle to do something.
For each such command, the Intepreter will call a method in the
Turtle class (such as ).
It will also add a delay (for move, turn, and home),
based on the GUI speed control, so that the user can watch the turtle move.
The turtle will keep track of its state (position, direction, pen, color, speed,
whether paused).
The commands that the turtle receives will affect what is drawn on the screen
(the TurtleCanvas). The Turtle class will create a
TurtleCommand to send to the TurtleCanvas. The TurtleCanvas
keeps a list of all the TurtleCommands it has received, and executes
them whenever the screen has to be repainted, with no delay between commands.
TurtleCommand is an interface with one method, execute(),
that implementing classes must define. There should be several implementations
of TurtleCommand; I've provided two, a DrawLineCommand
that you will probably want to keep, and a DrawStringCommand, which
is a throwaway example.
The distance that the turtle is to move is given in pixels. This is
exact if the turtle is moving horizontally or vertically. However, if
the turtle is moving at an angle, you will need to use trigonometry to determine
the approximate location that the turtle will move to. It is a good idea
to store the turtle's x-y location as double values, and cast to
int as necessary; this will prevent roundoff errors from accumulating,
and will result in a visibly better drawing.
The direction that the turtle faces is given in degrees, according to
the following scheme: east is 0°, north is 90°,
west is 180°, and south is 270°. A turn to
one of these directions is to that many degrees; a
or implies a 90° turn, while a turn
left <expression>
means to turn that number of degrees.
All of our Logo2006 variables are integer, and do not need to be declared (they
are implicitly declared when they are first encountered by the Interpreter).
In this assignment, all variables are global variables, and are kept
in a HashMap, with their name as the key and their integer value
as the value. In your Interpreter, you will need the following
methods:
protected void store(String name, int value)
protected int fetch(String name)In the next assignment, store and fetch will be modified
to also handle local variables.
I haven't been sitting still; some of these classes have been modified. Here are the latest versions. (I intend to post Parser.java before much longer.) You should use your own classes as much as possible, and only use mine if absolutely necessary.
To be determined, but not earlier than March 23.
Turn in your program via Blackboard, and follow these conventions:
package declarations in your Java files.It is your responsibility to check and make sure that you have correctly submitted the correct files via Blackboard. Errors in the submission process will not be taken as an excuse for late homework.