5. Nested Expression Updated

advertisement
Comp 114, Fall 2003
Updated Assignment 5: Nested Expressions
Date Assigned: Mon Oct 20, 2003
Parts1, 2, 3 and 4 Completion Date: Mon Nov 3, 2003
Parts 5 and 6 Completion Date: Mon Nov 10 2003
Objectives







More practice with inheritance.
Recursive tree traversal.
Composite design pattern.
List-based recursion.
Recursive tree creation.
Factory design pattern.
Façade design pattern.
Instead of only evaluating simple expressions, you will now evaluate parenthesized or nested expressions such as
5 + (2 * 3). This assignment has been broken into several parts to ensure you don’t take any missteps. The flip
side is that you don’t get to think as much on your own. Therefore, before you begin the assignment, think on
your own how you might evaluate such expressions. This will help you appreciate the path laid out for you. At the
end of this assignment you will have two scanner and expression object pairs, one for (unparenthesized) simple
expressions and one for parenthesized expressions. You will build factory and façade classes that easily allow a
main program to switch between these pairs. This assignment will give you experience with a wide variety of
concepts. The hardest part of this assignment is recursion. This part requires very few lines of code – the tricky
aspect is getting this code right. You have plenty of time to write this code. I recommend that you use it to first
solve, on your own, the recursive problems discussed in class, and then attempt the assignment.
Part 1: The Looking ahead part from previous assignment




Add null constructors and public init() methods to the scanner and expression classes that take the same
parameters as the non-null constructors in these classes. Add the init() methods in interfaces that extend your
existing interfaces. The null constructors should do no initialization – the idea is that init() methods will be
called after the object is created to do the initialization. This you will have the following two alternative ways
of creating and initializing a class C:
new C (p1, p2, …)
(new C()).init(p1,p2, ..)
Add new token classes, AnExpressionStart() and AnExpressionEnd(), representing the tokens ‘(‘ and ‘)’,
which will be legal when we allow expressions to contain subexpressions.
Create a new scanner class that creates these tokens. To get full points for this part of the assignment, the new
scanner class must be a subclass of your previous scanner class. It should be responsible only for detecting
and producing the two new tokens, reusing the code for the other tokens from the scanner for the previous
assignment. For those of you who have had trouble properly subclassing scanners, last Friday’s recitation
showed how scanners can be subclassed. Talk to the TAs or me if you still don’t understand.
Demonstrate the new scanner class by printing, in a main class, token sequences involving ‘(‘ and ‘)’, as
shown in the figure below.
Figure 1 Recognizing ‘(‘ and ‘)’ as legal tokens
Part 2: Printing an expression and adding elements to it
We are assuming that you have by now created two different classes, a scanner class that produces token
sequences and an expression class that stores and evaluates these sequences. Add a toString() method to the
expression class that prints the stored tokens within square brackets. Change your main class to use
System.out.println() to print the expression object it creates, which, recall, will simply call the toString() method
you added. If your expression class used the Java Vector class to store the tokens, your toString() can simply call
the Vector toString(), which prints its elements in the required format. If you used an array, you will have to write
your own toString() from scratch, and can use as a guideline the method we will see in class to print an array.
Since your expression class currently evaluates non-nested expressions, enter only such expressions as your
expression evaluator will give a parsing error for the two new tokens. In fact, you can use the scanner from the
last assignment for this part of the new assignment.
Now write one or more addElement() methods in your expression class that allows an external class to add
elements to the token list stored by an expression. You should make sure only legal tokens can be added to the
class, which are operators and numbers. These method will be used via inheritance in part 3 and directly in part 6.
Part 3:Creating and Printing Expression Tree
Now create a new expression class (with a different name or in a separate package) supporting hierarchical
expressions or expression trees. Let us call the original expression class as the simple expression class and the
new class as the nested expression class. The nested expression class should allow an expression to contain not
only tokens but also expressions themselves, as shown in Figures 2 and 3. The purpose of creating an expression
tree is to evaluate parenthesized expressions. A later part requires you to automatically create expression trees by
parsing parenthesized expressions. For now, create these manually in the main method by directly calling
addElement() methods in the expression trees, much as the fillCourses() method we will see in class does. As
before, allow external classes to add legal elements to the expression. Legal objects in the trees are numbers, the
operations, and expressions themselves. AnExpressionStart and AnExpressionEnd are not legal tree objects; they
should never be stored, even when you parse parenthesized expressions. You will need the null constructor from
part 1 for this part of the assignment – so finish part 1 if you have not already done so.
You should be able to easily make the nested expression class a subclass of the simple expression class. Create
expression trees and print them as in the previous part. You should not have to add any special code to print the
trees. Figure 3 shows the printing of the expression trees of Figure 2.
NestedExpression
NestedExpression
5
NestedExpression
3
/
2
4
*
+
NestedExpression
NestedExpression
3
4
/
*
NestedExpression
2
3
/
2
Figure 2 Expression Trees
Figure 3 Printing of the expression trees of Figure 2
Part 4: Printing and Evaluating Expression Tree
You should now evaluate and print the nested expressions. Not all expresion trees can be evaluated – just as not
all token sequences can be evaluated. Figure 5 shows an example of an illegal expression tree – a nested
expression rather than an operator folllows the number 5. A legal nested expression tree is like a legal simple
expression except that an expression (called a subexpression) can replace a number (but not an operator). The
evaluation of a nested expression is like the evaluation of a simple expression except we use the value of the
subexpression rather than the number it replaces. You have to recurse to evaluate subexpressions much as we had
to recurse to match titles in hierarchical course lists. Figure 4 shows how a nested expresssion evaluates– the
underlined values represent the result of evaluating each nested expresion. If you have solved the hierarchical
course list problem on your own, this part should not be too difficult. Unless you were far sighted when you did
your previous assignment, you will have to create new interfaces and redo one or more of your simple expression,
token, and nested expression classes, implementing a composite design pattern. Don’t worry about adding this
new code through inheritance – you can directly modify these classes. You may be able to educe the number of
addElement() methods in the simple and nested expressions after you have defined new interfaces.
11
NestedExpression
NestedExpression
6
5
+
NestedExpression
5
NestedExpression
1.5
4
*
3
NestedExpression
/
4
2
Figure 4 Evaluating a Legal Expression Tree
*
3
NestedExpression
/
Figure 5 An Illegal Expression Tree
Figure 6 Evaluating and Printing the Expression Trees of Figure 3
As part of your writeup, trace the execution of your recursive function much as we traced matchTitle() in class,
giving the object, argument and result of each execution. In your trace, use an expression tree with at least three
levels (as in figure 4) but make it different from figure 4.
Part 5: Parsing a nested expression
Now write a recursive method to create an expression tree automatically from tokens input by the user, using the
scanner from part 1, and as in part 4, print and evaluate the tree. Give parsing errors for unmatched parentheses.
Figure 7 shows the desired behavior.
Unlike the analogous function for creating nested course lists from nested arrays, the recursive method should be
an instance method in the object – in fact it should be an init method shared with the simple expression class in
order to do the next part. The init method can recurse by calling itself on the root object, or by calling itself on
sub objects. While I implemented the latter approach, I think you will find it easier to take the former approach.
In either case, the method will take the token stream as a parameter, and thus will implement list-based recursion.
However, unlike the list-based recursion examples we discussed in class, it will not recurse after consuming each
list element. Instead, it will recurse after consuming a variable-sized sequence of items. Before you write this
method, in your writeup, explain the conditions under which the function recurses and returns. You can use loops
in this method to go through tokens that are processed before the recursive step.
Part 6: Creating a factory and facade
You now have two sets of matching scanners and expressions, one that scans and evaluates simple expressions
and another that scans and evaluates nested expressions. Create a factory for each set, and a factory selector that
selects between the two factories. The two factories should implement the same interface.
Write a façade that combines the expression and scanner objects returned by the factory selected by the factory
selector into one object capable of evaluating expression strings input by the user. This method will call the init
methods of the two objects. This implies that the two scanner classes should share the header of a common init
method, as should the two expression classes. Write a main class that uses this façade.
Make sure that the façade creates a single expression object to process all input strings submitted to it by the
main class rather than a separate expression object for each string, which is what you probably have been doing
so far. This is important to allow it to become a spreadsheet cell. With a separate init method, this is possible.
However, you have to make sure that the init method in both simple and nested expressions resets its token list.
(Feel free to call it a set method as it can now be called to reinitialize.) Even though this part does not require
you to print the expression object before evaluating it, you may want to do so for debugging purposes specifically to make sure the token lists are reinitialized. Also, you may want to change the toString() in simple
expression (inherited by nested expression) to prefix the default toString() inherited from Object to the token list,
as you did for tokens. The default toString() prints the object address after the class name. You know you are
using the same expression object for different input expressions, if the same address is printed before the different
token lists created for the expressions. We strongly recommend adding the prefix and printing of the expression
object after each input expression string – it will help identify errors in this and later assignments.
Make your factory selector choose the simple expression and scanner and run the main class (Figure 7). Next
make it choose the nested expression and scanner and run the main program (Figure 8). You should not change
the façade and main classes between these two runs – only the factory selector class.
Figure 7 Main program and façade using one selection of the factory selector
Figure 8 Same main program and facade using the other selection of factory selector
Instructions for First Submission
Submit:
1. A printout of all of the classes and interfaces you end up with after completing part 4.
2. Screenshots to show parts 1 and 4 working. As always, you must select appropriate test cases to demonstrate
your solutions, in particular, those used in the screen shots given above. In all assignments, you will be graded
on the test cases you choose.
3. Writeup tracing the recursive expression evaluation function.
As always, upload the assignment directory to by midnight of the day the assignment is due and do not change the
code after you submit it in class.
Instructions for Second Submission
Submit:
4. A printout of all of the classes and interfaces you end up with after finishing part 6.
5. Screenshots to show parts 5 and 6 working. As always, you must select appropriate test cases to demonstrate
your solutions, in particular, those used in the screen shots given above. In all assignments, you will be graded
on the test cases you choose.
6. Writeup explaining the conditions under which the recursive parsing function recurses and returns.
As always, upload the assignment directory to by midnight of the day the assignment is due and do not change the
code after you submit it in class.
This is probably the hardest assignment of this class. Good luck.
Download