This assignment highlights the importance of modeling and design in
object-oriented programming.
You are given code that implements a simple numerical calculator, which
you will gradually enhance. The code benefits from modeling and design
because:
- It is based on a state transition model that is easy to modify
and extend, and
- It benefits from code reuse provided by class inheritance
This section describes how the code you are given has been designed,
and how you will modify both the design and its implementation.
Make sure you understand the aspects of this design.
You will want to return to this section when you are ready to proceed
with the assignment.
See the menu for provided class documentation.
In the NetBeans lab exercise (see menu to the left), you were given
a
Console class that presented a window in which input could be
entered and results echoed to the display area (see right top).
Console makes use of the JavaFX GUI framework, which you have
yet to learn. At this point, however, that does not matter, because
Console is designed to be extended by classes that do not have to do
any GUI programming — they only have to provide code that handles
input in a specific way.
You are given a
SimpleCalculator class that
extends
Console and allows simple arithmetic expressions to be
evaluated (see right bottom).
The inheritance relationship between
SimpleCalculator
and
Console is shown in UML as:
In order to evaluate arithmetic expressions, we need to read and return
the various basic elements of the expressions, including numbers,
operators, and the left and right parentheses.
This process, called
parsing, is supported at a rudimentary
level by the
StreamTokenizer class in the
java.io package
of the Java Standard Library.
You are given a custom
TokenDispenser class that extends
the
StreamTokenizer library class for the purposes of arithmetic
expression evaluation:
SimpleCalculator overrides the
handleInput method of
the
Console class by handing the input to a stack-based
shift/reduce expression evaluator, implemented by the
evaluate
method.
The
evaluate method is modeled by the state diagram you produced
for the UML lab exercise (see diagram at right; see UML lab exercise in
menu to the left).
The evaluator for this state machine is simple: it can only evaluate
expressions like "
17.4" and "
10/3". It cannot evaluate
"
2*3+4", for example.
In Backus-Naur form (BNF), the evaluator can evaluate expressions of
the form
NUM [OP NUM] (
NUM is a number,
OP is an
operator, and
[...] means "zero or one occurrences
of
...").
Use the menu at left to follow an example stack trace on
10/3.
When the evaluation begins, the stack is empty:
The first token,
10, is obtained and shifted onto the
stack. The machine state is
Number1:
The second token,
/, is obtained and shifted onto the
stack. The machine state is
Operator:
The third token,
3, is obtained and shifted onto the
stack. The machine state is
Number2:
Since there are no more tokens, the token dispenser indicates this
with an EOF indication, the machine goes into state
End, and
the stack is reduced to the value of
10/3. The value of the
expression is left as the only element on the stack:
A simple change to the initial state diagram produces a model for a
calculator that can evaluate
2*3+4 as
10. The same
change will evaluate
2*3+4*5 as
50.
Note that this new evaluator does not recognize operator precedence. If
it did,
2*3+4*5 would evaluate as
26.
In BNF, this evaluator can evaluate expressions of the form
NUM [OP
NUM]* (
[...]* means "zero or more occurrences
of
...").
You will create a class called
NoPrecedenceCalculator that
implements this new evaluation model. It can reuse code by extending
SimpleCalculator but it must override the
evaluate
method:
Operator precedence requires that the
* and
/ operators
have higher precedence than
+ and
-.
Enforcing operator precedence requires scanning an expression for
consecutive operators, and if the second operator has higher precedence
than the first, it must be applied before the first is applied. Thus,
2*3+4*5 evaluates as
26.
Handling precedence in our evaluator does not require any change to the
structure of the state machine; it only requires a change in the way
the stack is reduced.
You will create a class called
PrecedenceCalculator that will
inherit from
NoPrecedenceCalculator but override
the
reduce method as shown below.
Operator precedence introduces the possibility that the reducing
process needs to iterate. See the menu at left for an example.
When evaluating
2+3*4, the stack will come to look like this:
When the token dispenser indicates no more input, the state machine
must reduce the stack:
The stack must be reduced again to finish the input:
Note that only one token, the EOF indicator, triggers these two
reductions.
Useful calculators allow parentheses to override the usual precedence
rules. For example,
2*(3+4)*5 evaluates as
70.
Handling parentheses in our state diagram requires new states
corresponding to the left and right parentheses, and new transitions in
and out of them. Thus the
evaluate method must be overridden.
Since encountering a right parenthesis triggers stack reductions
the
reduce action must also be changed.
You will create a class called
ParenthesisCalculator that will
inherit from
PrecedenceCalculator but override
the
evaluate and
reduce methods:
This section walks you through the process of turning
the
SimpleCalculator into one which handles precedence and
parentheses.
At each step, you will modify the
simple calculator state diagram that you created
for the UML lab exercise.
These diagram modifications are an important part of the modeling and
design process, and will be graded along with your code.
You must save a copy of the diagram at each step and include it as part
of your submission.
Download this
Calculator.zip
file and use the NetBeans
Import Project
feature to create the
Calculator project.
Your project should have this structure:
Test the
SimpleCalculatorTest.java file. You should get a window
like:
Try various inputs to observe its behavior. Then close the window by
clicking X on the window's title bar. The test cases coded in
the
SimpleCalculatorTest class will be run and you should get
these results in the
Test Results
window:
In the
Output window, you should also see:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running calculator.SimpleCalculatorTest
testResults passed
testErrors passed
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
In this step you will implement a calculator that can evaluate inputs
of arbitrary length, without accounting for operator precedence. You
will:
- Alter the original state diagram
- Create a new class that implements the new state transition
model
- Test the new class with provided test cases
This general procedure will be followed for the remaining steps as well.
Since operator precedence is ignored, the stack is reduced when
possible. Here is a trace of the stack when evaluating
2 + 3 * 4:
Using
Violet, open the
State.violet file that
you created and submitted for the UML lab exercise:
- Change the title note to read "State Diagram for a Shift-Reduce
Calculator Without Precedence for Expressions of the form NUM [OP
NUM]*"
- Change the diagram itself so that it conforms with the new
title. Hints:
- To handle expressions of arbitrary length, a
single Number state should replace
the separate states Number1 and Number2.
The diagram should loop back to Number as appropriate.
- The action of the Operator state must be
modified. What must happen if an operator is encountered
and NUM OP NUM is already on the stack?
- Use Violet's File → Export to → Image
file option to save your diagram
as no-precedence.png
You need to complete the
NoPrecedenceCalculator
class, shown below.
As described in the Design
section, this class must be a subclass of
SimpleCalculator to
inherit its features. A class template is given for you in the project:
This class needs to implement the
evaluate method, overriding
the one in
SimpleCalculator. Look at how
SimpleCalculator
implements the original state diagram, then write your
evaluate
method to implement your modified diagram. Hints:
- Note that SimpleCalculator defines a public enumerated
type called State one of whose values is NUMBER
- You will need to use the inherited getDispenser method so
that you can advance the token dispenser as needed
- Like the original evaluate method, yours will need to
loop until the input is exhausted, advancing to the next token,
shifting tokens onto the stack, and reducing the stack as
necessary
- Be sure that each time through the loop the
inherited setState method is used to change the state of
the evaluation
- Be sure that whenever a token is acquired for which your state
diagram is not defined, a syntax error is signaled using the
inherited syntaxError method. Take a look at the test file
NoPrecedenceCalculatorTest.java (see Provided Files) for
examples of erroneous input and the exact wording of error messages.
Test the
NoPrecedenceCalculatorTest.java class, shown
below.
As with the simple calculator test, you will first be able to
enter some expressions manually. After exiting the calculator,
the coded tests will be run and if successful you will get the
same results as with the simple calculator.
Don't move to the next
step until you've passed this one.
In this step you will implement a calculator that can evaluate inputs
of arbitrary length, accounting for operator precedence, but not
allowing parentheses.
Since operator precedence is respected, the stack is reduced according
to precedence rules. Here is a trace of the stack when evaluating
2 + 3 * 4:
Using
Violet:
- Change the title note to read "State Diagram for a Shift-Reduce
Calculator With Precedence and Without Parentheses for Expressions
of the form NUM [OP NUM]*"
- Change the diagram itself so that it conforms with the new
title. Hints:
- The states and state transitions themselves do not need to be
changed
- The action taken when reducing the stack (as indicated in the
diagram note) must be changed:
- If NUM OP NUM is on the stack and the end of the
input is reached, what must happen?
- If NUM OP NUM is on the stack and the next token
is an operator, what must happen?
- Note that the reducing process may have to repeat. For
example, when the end of 2+3*4 is reached, the stack
must reduce to 2+12 and then immediately
to 14
- Use Violet's File → Export to → Image
file option to save your diagram
as with-precedence.png
As described in the Design
section, this class must be a subclass of
NoPrecedenceCalculator to
inherit its features. A class template is given for you in the project:
This class needs to implement the
reduce method, overriding
the one in
SimpleCalculator. Write your
reduce
method to implement your modified diagram. Hints:
- Several methods inherited from SimpleCalculator,
namely numOpNumOnStack and reduceNumOpNum, are
useful
- You will need to use the inherited getDispenser method so
that you can use TokenDispenser methods such
as tokenIsEOF and tokenIsOperator
- In order to peek down the stack without popping, you can use
the get method that the Stack class inherits
from Vector (see the java.util package)
- To handle the possibility of multiple reductions for one input
token, it is helpful to employ a while loop controlled by a
boolean valued method that returns whether the stack is
reducible
Test the
PrecedenceCalculatorTest.java class, shown
below.
As with the simple calculator test, you will first be able to
enter some expressions manually. After exiting the calculator,
the coded tests will be run and if successful you will get the
same results as with the previous tests.
Don't move to the next
step until you've passed this one.
In the final step you will implement a calculator that handles both
operator precedence and parentheses.
The stack is used to both enforce operator precedence and respect
parenthesis levels. Here is a trace of the stack when evaluating
(2 * (3 + 5)):
Using
Violet:
- Change the title note to read "State Diagram for a Shift-Reduce
Calculator With Precedence and Parentheses*"
- Change the diagram itself so that it conforms with the new
title. Hints:
- There need to be new states corresponding to the left and
right parentheses, with appropriate transitions into and out
of them
- Make sure that transitions exist for every legal occurrence
of left and right parentheses
- The action taken when reducing the stack (as indicated in the
diagram note) must be changed:
- When a right parenthesis is encountered, the same
(possibly multiple) reductions must be triggered as when
EOF is encountered
- If ( NUM ) is on the stack, what must happen?
- If ( NUM OP NUM ) is on the stack, what must happen?
- If ( NUM OP NUM OP NUM) is on the stack, what must happen?
- When the end of input is reached, one of the actions must be
to check for mismatched parentheses. Mismatched parentheses
occur if the end of input has been reached and the stack does
not contain a single number.
- Use Violet's File → Export to → Image
file option to save your diagram
as with-parens.png
As described in the Design
section, this class must be a subclass of
PrecedenceCalculator to
inherit its features. A class template is given for you in the project:
This class needs to override both the
evaluate and
reduce
methods to implement your modified diagram. Hints:
- Note that SimpleCalculator's public enumerated
type State includes the values LEFT_PAREN
and RIGHT_PAREN that can be used by evaluate
- Take a look at the test file
ParenthesisCalculatorTest.java (see Provided Files) for
examples of erroneous input and the
exact wording of error messages.
- When the end of input has been reached and all reductions have
been performed, if it is not the case that there is a single
element (the expression value) on the stack, then there have been
mismatched parentheses
Test the
ParenthesisCalculatorTest.java class, shown
below.
As with the previous tests, you will first be able to
enter some expressions manually. After exiting the calculator,
the coded tests will be run and if successful you will get the
same results as with the previous tests.
The files required for this assignment are available from the menu on the
left.
When your calculator project is working correctly:
- Make sure your identifying information is in the class comments
of all class files
- Use the NetBeans Export Project
feature to create
Calculator.zip in a location other than
your Calculator project.
- Submit the following four files by going to
and clicking Submission under Assignment:
The Importance of Modeling and Design.
- no-precedence.png (state diagram for Step 1)
- with-precedence.png (state diagram for Step 2)
- with-parens.png (state diagram for Step 3)
- Calculator.zip (your exported project)
Note the general
Submission Policy in the menu at left.
Your project will be inspected and run by the lab instructor. For full
credit your project must pass the provided tests.
Grading criteria:
- Successful completion of Step 1 including correct state
diagram: 8 points
- Successful completion of Step 2 including correct state
diagram: 8 points
- Successful completion of Step 3 including correct state
diagram: 14 points