import java.io.FileReader;
import java.util.ArrayList;
import java.util.Scanner;
/**
* A class that implements ICS 211 Homework 4, Infix Calculator. The infix
* calculator accepts input from a file specified as a command-line parameter.
* It reads each line of the file. For each line, it considers each character of
* the line, one at a time, taking the appropriate action for each character.
*
* @author Cheng Jade
* @assignment TA 4
* @date Feb 08, 2010
* @bugs None
*/
public class ChengJade04 {
/**
* Compares two operators. This method does not check to see if the precedence
* operators are valid.
*
* @param lhs The operator on the left hand side.
* @param rhs The operator on the right hand side.
* @return A value greater than zero if the operator on the left hand side is
* greater than the operator on the right hand side, a value less than
* zero if the operator on the right hand side is greater than the
* operator on the left hand side, or zero if the two operators have
* the same precedence.
*/
private static int compareOperator(final char lhs, final char rhs) {
// return the difference in the operator precedence values.
return getPrecedence(lhs) - getPrecedence(rhs);
}
/**
* Gets an integer value that represent's an operator's precedence. This
* method does not check to see if the precedence operator is valid.
*
* @param op The operator.
* @return An integer value that represent's the operator's precedence.
* Multiplication, division, and modulus have the same precedence
* values, and they are higher in precedence than addition and
* subtraction, which have the same precedence value.
*/
private static int getPrecedence(final char op) {
// return 1 for the add and minus operators.
if ("+-".indexOf(op) >= 0)
return 1;
// return 2 for multiply, divide, and modulus operators.
assert "*/%".indexOf(op) >= 0;
return 2;
}
/**
* Computes the result of an operation on two values. This method does not
* check to see if the operator is valid.
*
* @param lhs The value on the left hand side of the operator.
* @param op The operator.
* @param rhs The value on the right hand side of the operator.
* @return The result from the operator.
* @throws CalculatorException Thrown if there is division or modulus by zero.
*/
private static int compute(final int lhs, final char op, final int rhs)
throws CalculatorException {
// check for overflow while calculating the results.
if (op == '+')
return checkOverflow((long)lhs + (long)rhs);
if (op == '*')
return checkOverflow((long)lhs * (long)rhs);
if (op == '-')
return checkOverflow((long)lhs - (long)rhs);
// check for division by zero.
if (rhs == 0)
throw new CalculatorException("Division by zero.");
if (op == '/')
return checkOverflow((long)lhs / (long)rhs);
assert op == '%';
return checkOverflow((long)lhs % (long)rhs);
}
/**
* Pops two values off a number stack, computes the result based on the
* operator, and return the result. This method does not check to see if the
* operator is valid.
*
* @param numberStack The number stack.
* @param op The operator.
* @throws CalculatorException Thrown if there is division by zero or if the
* number stack is empty.
*/
private static void popComputePush(final Stack<Integer> numberStack,
final char op) throws CalculatorException {
// pop two values off the stack, compute the result, and return the result.
final int rhs = numberStack.pop();
final int lhs = numberStack.pop();
final int k = compute(lhs, op, rhs);
numberStack.push(k);
}
/**
* Evaluate if the operator stack and number stack are valid. The number
* stack should always have more or same number of elements versus the
* operator stack for a valid infix calculator expression.
*
* @param numberStack The number stack.
* @param operatorStack The operator stack.
*
* @return True if the stacks are valid or false if otherwise.
*/
private static boolean validStacks(Stack<Integer> numberStack, Stack<Character> operatorStack) {
return !(operatorStack.getSize() > numberStack.getSize());
}
/**
* This is the main entry point of the application.
*
* @param args The command line arguments. The user should specify a file with
* a list of lines to calculate.
*/
public static void main(final String[] args) {
// declare two stacks, one for the numbers, one for the operators.
final Stack<Integer> numberStack = new Stack<Integer>();
final Stack<Character> operatorStack = new Stack<Character>();
// check if only one file is passed in as the command line input.
if (args.length != 1) {
System.err.println("usage: ChengJade04 <path>");
System.exit(1);
return;
}
try {
// declare a FileReader to read the input file and also declare a Scanner
// to read through the FileReader.
final FileReader reader = new FileReader(args[0]);
final Scanner scanner = new Scanner(reader);
// the scanner loops through the file while there are more lines to read.
while (scanner.hasNextLine()) {
// initialize a String for the next line of the file.
final String line = scanner.nextLine();
// ignore the line if it's a blank line.
if (line.length() == 0)
continue;
// check for exceptions here only to display the line that failed in the
// outer catch block.
try {
// loop through each line checking each character from left to right.
for (int i = 0; i < line.length(); i++) {
// at any state, the operator stack can't have more elements than the
// number stack for a valid infix calculator expression.
if (!validStacks(numberStack, operatorStack)) {
System.err.println("Invalid Infix Calculator.");
break;
}
final char c = line.charAt(i);
// if it's a number, push it into the number stack.
if (Character.isDigit(c)) {
numberStack.push(c - '0');
continue;
}
// if it's a space, ignore it and move on to the next character.
if (Character.isSpaceChar(c))
continue;
// check for an invalid character.
if ("+-*/%".indexOf(c) < 0)
throw new Exception("Invalid character: '" + c + "'");
// loop until the operator stack is empty or if the new operator is
// greater than the top operator.
while (!operatorStack.empty()) {
// check if the top operator is less than or equal to the new
// operator in precedence.
final char topOperator = operatorStack.pop();
if (compareOperator(c, topOperator) > 0) {
// in this case push the operator back onto the stack and stop
// looping
operatorStack.push(topOperator);
break;
}
// pop two numbers off the stack, compute their values, and
// repeat.
popComputePush(numberStack, topOperator);
}
// push the new operator onto the stack.
operatorStack.push(c);
}
// now that end of the line was reached, loop until there are no more
// operators on the stack.
while (!operatorStack.empty()) {
// pop two numbers off the stack, compute their values, and repeat.
final char topOperator = operatorStack.pop();
popComputePush(numberStack, topOperator);
}
// check that there is only one left value on the stack.
if (numberStack.getSize() != 1)
throw new CalculatorException("Not enough operators.");
// display the line and the result.
final int result = numberStack.pop();
System.out.println("'" + line + "' = " + result);
} catch (final Exception e) {
// rethrow the exception to display the line that caused the failure.
throw new CalculatorException("Error on line '" + line + "': "
+ e.getMessage(), e);
}
}
} catch (final Exception e) {
// in case of any errors, display the message, and exit with a failure.
System.err.println(e.getMessage());
System.exit(1);
return;
}
// return zero to let the eclipse debugger close happily
System.exit(0);
}
/**
* Checks to see if the parameter (of type long) passed in is in the range of
* an integer type. If it is, the same value is returned casted back to an
* integer.
*
* @param n the number to be checked.
* @return The same value casted back to an integer.
* @throws CalculatorException Thrown if the value is not in the range of an
* integer type.
*/
private static int checkOverflow(final long n) throws CalculatorException {
// check for underflows.
if (n < Integer.MIN_VALUE)
throw new CalculatorException("Integer underflow encountered: " + n);
// check for overflows.
if (n > Integer.MAX_VALUE)
throw new CalculatorException("Integer overflow encountered: " + n);
// return the same value casted back as an integer type.
return (int)n;
}
}
/**
* A generic stack class.
*/
class Stack<T> {
/** An array to store the stack elements. */
private final ArrayList<T> array = new ArrayList<T>();
/**
* Pushes an item onto the stack.
*
* @param item the item to be pushed onto the stack.
*/
public void push(final T item) {
assert item != null;
array.add(item);
}
/**
* Pop an item off the top of the stack.
*
* @return the last item that was pushed onto the stack.
* @throws CalculatorException Thrown if the stack is empty.
*/
public T pop() throws CalculatorException {
// check the stack is not empty.
if (array.isEmpty())
throw new CalculatorException("The stack is empty.");
// remove the last item added to the stack and return it.
final int top = array.size() - 1;
final T result = array.get(top);
array.remove(top);
return result;
}
/**
* Returns true if the stack is empty.
*
* @return True if the stack is empty or false if the stack is not empty.
*/
public boolean empty() {
return array.isEmpty();
}
/**
* Returns the size of the stack.
*
* @return the number of items that can currently be popped off the stack.
*/
public int getSize() {
return array.size();
}
/**
* Returns the contents of the stack as a string.
*
* @return the contents of the stack as a string.
*/
@Override
public String toString() {
// return a special message if the stack is empty.
if (array.size() == 0)
return "The stack is empty.";
// build a list of items.
final StringBuilder builder = new StringBuilder();
builder.append("{ ");
for (int i = 0; i < array.size(); i++)
builder.append(array.get(i) + " ");
builder.append("}");
// return the list of items that looks like "{ a b c ... }".
return builder.toString();
}
}
/**
* An exception class that is thrown and caught by the program.
*/
@SuppressWarnings("serial")
class CalculatorException extends Exception {
/**
* Initializes a new instance of the class.
*
* @param message The message.
*/
public CalculatorException(final String message) {
super(message);
}
/**
* Initializes a new instance of the class.
*
* @param message The message.
* @param throwable The object that caused the exception.
*/
public CalculatorException(final String message, final Throwable throwable) {
super(message, throwable);
}
}