CS 210 Intermediate Computing with Data Structures
Programming Assignment 3
Spring 2006

Due Monday Mar 20 at noon

Purpose

This assignment aims to help you:

·          Gain experience with the Stack ADT and with linked lists

·          Work with tokenization again, this time with Token objects

·          Have experience in refactoring classes

·          Learn about an interesting algorithm used in compiler design

Reading

Before working on this assignment, you should read 6.6.4, 11.1, 11.2, and 16.4 in the text.

Description

There are two parts to this assignment:

1.       Implement a pure Stack, the JDK Stack subset of Figure 16.28, (not the Weiss nonstandard API, pg. 226)  using JDK LinkedList—see Weiss 16.4 for most of this code, but drop size() as not in the "pure" subset.  Call the class PureStack, to avoid naming conflicts with Stack in the JDK and make clear what this is. Replace the get() on line 31, pg. 558 with an appropriate LinkedList method call.  Add a toString() method that mimics a Collection toString(), making sure that the top of stack is on the right-hand end.


2.       Following the steps listed below, use your stack class to build an implementation of Djiksta’s “shunting algorithm” for calculating the value of arithmetic expressions such as (1 + 2*3)/2.  The shunting algorithm is used to calculate the values of arithmetic expressions taking into account parenthesis and operator precedence.  For this assignment we are going to work with integer values (int’s) and integer operators.  The operators we will use are ^, *, /, %, +, -, &, and |.  These operators have the same meaning they do in Java except for ^, and ^ is exponentiation, as described in Weiss, pg 380.  Recall that | is bitwise OR and & is bitwise AND.

The shunting algorithm works with two stacks: opStack - a stack of operators and postfixStack - a stack of values.  We scan through an expression left to right.  Each operator, operand, or parenthesis encountered is handled in turn.  Evaluating the top consists of popping operator op from opStack, and popping val2and val1 from postfixStack, calculating val1 op val2, and pushing the result onto postfixStack., in other words, doing a postfix stack operation.

We will use the following table of operator precedence.  All operators except ^ have left-to-right associativity.  Thus 24/6/3 means (24/6)/3, not 24/(6/3) .  But 2^3^2 means 2^(3^2).

Very high:

^

High:

*, /, %

Medium:

+, -

Low:

&

Lower:

|

The shunting algorithm works by scanning through the expression and considering each token in turn:

·          If the token is an integer, push it onto postfixStack.

·          If the token is a left parenthesis (the ‘(‘ symbol), push it onto opStack.

·          If the token is a right parenthesis (the ‘)‘ symbol), then you are evaluating a sub-expression.  Repeat evaluating the top until the top of opStack is a left parenthesis.  Finally, pop the left parenthesis from the top of opStack.

·          If the token is an operator, do the following.  While an operator on the top of opStack exists and has higher precedence (or is left-to-right associative and has equal precedence) than the encountered operator, repeat evaluating the top, Finally, push the encountered operator on the opStack.

·          When the entire expression has been scanned, repeat evaluating the top until opStack is empty.  The value on top of postfixStack is the value of the expression. 

Steps

Note that these steps can be done in almost any order.  Giving them numbers will make it easier to refer to them when asking for help, etc.

Start from Weiss’s calculator of 11.2, which already can do this job for most of the operators, and is available in Weiss’s website, and handles associativity via special precedence scores for operators.   Also note that Weiss gives parentheses a precedence score, somewhat artificial, and uses another artificial token EOL.  It would be nice to undo these tricks, but I’ll leave that up to you.

After completeing each step, firest make sure everything works right, and then make a backup copy of your directory, so you can go back to it if you get in trouble during the next step.

  1. Download Weiss's calculator in Evaluator.java available at his website linked from the class web page.  All you need to do it change "import weiss.util.Stack" to "import java.util.Stack" at the top. Build it and try it out by entering "2+3*4", "(2+3)*4", etc.
  2. Get the inner classes out of the main class to simplify our understanding of this system:  Here's the new organization. 

    Token.java:  the inner class of the same name now a public class, with the token constants now made public, like "public static final int VALUE   = 1;"
    EvalTokenizer.java:   from the inner class of the same name with EXP now Token.EXP, etc
    Precedence.java: from the inner class.  Note that the precedence array stays in Evaluator.java.
    Evaluator.java: what's left, with VALUE now Token.VALUE, etc.


    Make sure this version works as before.
  3. Replace the java.util.Stacks with your PureStack objects.
  4. Replace opStack with a PureStack of Token.  This will allow us to print out opStack more easily. This will turn "opStack.peek()" into "opStack.peek().getType()", etc.
  5. Provide Token with a toString that turns a Token into an appropriate mark like *, +, etc., and VALUE Tokens into their numeric value.
  6. Make processToken print out a line for each token it processes, showing the token (lastToken), the opStack starting at column 5, and the postfixStack starting at column 30.  Use toString of PureStack to get a String for each stack, and then string length to figure out how many blanks to output to get to the right column for the second stack printout.
  7. Seal up Precedence.java.  This means make the fields private.  You will need to add getters and use them where the fields are now used via public access.
  8. (This step dropped.) Replace the StringTokenizer with a Scanner, and hide it inside EvalTokenizer.  Evaluator should have a String field for the String it was created with, not a Scanner or StringTokenizer.
  9. In processToken, add another case OPAREN: so that the code more closely resembles the pseudocode above.
  10. Add the missing operators.

memo.txt

In the file memo.txt, answer the following questions:

·        What problems did you encounter and how did you solve them? 

·        What Java development environment did you use?  Any problems with it?

·        Did you access any information on the Web during this work?  Where?

.      Did you skip any of the steps?  Lack of time?  Got stuck?  or what?

·        Is a doubly linked list (as in JDK’s LinkedList) a good data structure for implementing a stack?  Why or why not? 

·        Do you think the program was improved by pulling the classes apart?  Discuss advantages and disadvantages of the two designs.

·        Explain what you would have to do to modify your implementation to use decimals like 1.45 in the calculations.

Turn In

Use the turnin system to submit your files.  Before the due date, submit the following files to the program2 folder of your turnin system account:

·          memo.txt

·          Token.java

·          EvalTokenizer.java

·          Precedence.java

·           Evaluator.java  with main().  Works the same way as the provided Evaluator program.

.   PureStack.java

We will run a program that compiles and tests the Java code, and collect all files for the grader. It is your responsibility to make sure those files are present, that their names are spelled correctly (e.g. we will not find Memo.txt or memo.TXT) and that the files are turned in for the proper assignment folder.