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.