Lab Assignment 9

  1. [solve] Your assignment is to write a program in Java that reads expressions involving real numbers from the user and writes their values. Each expression is on one line. After reading a line, the program should display the answer and read the next line. It should keep going until it reads a line that only contains character $.

    Here is a sample session. Parts in black are typed by the user. Parts in blue are written by the program.

    23.1 + 5.0
    28.1
    2*2*2
    8
    2+3*4
    14
    2*(3.1 + 4.1)
    14.4
    25/2
    12.5
    s(16)+1
    5
    $
    

    Follow the instructions to do this. The idea is to write the program using successive refinement, where you gradually add more and more functionality to the program. You test it as you go, using only that part of the functionality that you have implemented so far. That is how professionals develop large pieces of software.



    Program skeleton

    Open a text editor (gedit) and copy the following skeleton into the text editor. (Copy and paste. Do not type it yourself.)

    import java.io.*;
    import java.util.*;
    
    public class Evaluate
    {
      public static void main(String[] args)
      {
      }
    
    
    }
    


    Getting lines from the user

    You will be using Java objects of class Scanner. To build a new Scanner object called keyboard that reads from the keyboard, use statement

      Scanner keyboard = new Scanner(System.in);
    
    Then once the scanner has been created, expression
      keyboard.nextLine()
    
    reads and yields the next line of text (an object of class String). Each time you do that, you get another line. For example,
      String line = keyboard.nextLine();
    
    reads a line and calls it line.

    You can display string line (followed by an end-of-line marker) using

      System.out.println(line);
    

    Fill in the body of main by making it read lines until it reads one that starts with $. After reading each line that does not begin with $, add a $ to the end as an end marker. Then display the line. You should see all of the lines displayed.

    Important. Only create the keyboard object once. Do not build a new Scanner object each time you want to read another line.

    Test this before moving on.



    Reading from a string

    A definition of class CScanner is provided for you. Download it into the same directory as your program. (Right-click the link and select Save Link As.)

    Compile CScanner.java. Command

      javac CScanner.java
    
    will do that.

    Modify the body of main so that it creates a new CScanner object that reads from the line that you just read. Statement

      CScanner scan = new CScanner(line);
    
    will build the CScanner object and call it scan.

    The next section tells how to use the CScanner object.



    Expressions, terms and factors

    The expressions that your program should be able to evaluate allow real number constants such as 4.2, addition (+), subtraction (-), multiplication (*) and division (/). Multiplication has the same precedence as division and addition has the same precedence as subtraction. Multiplication has higher precedence than addition. Operations of the same precedence are done from left to right. (Note. Your expressions will not allow - to used as a negation operator. It must have two operands.)

    Additionally, the expressions allow parentheses to override precedence rules and expression s(A), which yields the square root of expression A.

    An expression is any kind of expression that the program supports.

    A term is an expression that does not allow the use of + or - unless they occur inside parentheses. For example, 2.0*5.0 is a term. So is (9.0 + 1.0), since the + operator occurs inside parentheses. But 2.0 + 4.0 is not a term since it contains a + that is not inside parentheses.

    A factor is an expression that does not allow any of the binary operators except within parentheses. For example, 5.2 is a factor, as is s(2.0*4.0). But 4.0*5.0 is not a factor since it uses a binary operator (*) that is not inside parentheses.

    You do not want to be required to deal with everything before you test anything. So, to get going, write a stub for methods getTerm and getExpression that you can use until they are written. For now, they just get a factor.

    
    public static double getExpression(CScanner scan)
    {
      return getTerm(scan);
    }
    
    public static double getTerm(CScanner scan)
    {
      return getFactor(scan);
    }
    

    Modify the body of main so that, instead of just displaying the line that it read, it uses getExpression(scan) to get the value of an expression, and prints the value of the expression. You will not be able to test it until you add getFactor method.



    Reading a factor

    Write a method called getFactor that reads a factor from a CScanner object and returns the value of the factor. For example, if the line is "40.1$", then getFactor would yield 40.1. The heading for getFactor should be as follows.

      public static double getFactor(CScanner scan)
    
    Include a contract for getFactor just above it. The contract is a comment that tells what getFactor does without going into details about how it works.

    To read a factor start by getting the first nonblank character. Expression scan.nextNonblankChar( ) yields the next nonblank character, and moves past it, so that successive uses of it will yield successive characters.

    A factor begins with either '(' or 's' or a digit. So there are three cases, plus one case for an error. Provide a case where the character is a left parentheses, one where the character is 's', and one where the character is a digit. Expression Character.isDigit(c) is true if character c is a digit.

    1. When the next character is a left parenthesis, get an expression (using getExpression), then get another character. Check that the character is a right parenthesis. If not, then throw an exception as follows.

        throw new Error("missing right parenthesis");
      
      Return the value that was given to you by getExpression.

    2. When the next character is 's', get another character. It should be a left parenthesis. If not throw an exception. If it is a left parenthesis, then get an expression (using getExpression) and get a character. It should be a right parenthesis. Return the square root of the value that was given to you by getExpression.

    3. When the next character is a digit, start by putting the digit back so that it will be read again. Statement

        scan.backup();
      
      will do that. Now get a real number, using expression
        scan.nextDouble()
      
      Return the value produced by scan.nextDouble().

    4. When the next character is anything else, throw an error.

    Test your program. It should work as long as you do not use any binary operators.



    Getting a term

    Add a contract for getTerm. Tell what it does.

    To get a term, start by getting a factor. Remember the value of that factor as your tentative result. (Let's call it result.) Then go into a loop. (You will be getting out of the loop by doing a return in the body, so heading while(true) will work.) Each time around the loop, do the following.

    1. Get the next character. (Call it c.) If c is not '*' or '/', then put it back and return the value of your current tentative result, since it is the final result.

    2. Get another factor. Suppose that its value is f.

    3. If c is '*', then set result = result * f. Continue to do another time through the loop.

    4. If c is '/', then set result = result / f. Continue to do another time through the loop.

    Test your program. It should work now as long as you do not use + or -.



    Getting an expression

    Add a contract for getExpression. Tell what it does.

    Getting an expression is a lot like getting a term. But instead of getting a factor each time, you get a term. Also, instead of checking for '*' or '/', you check for '+' or '-'. Instead of multiplying or dividing, you add or subtract.

    After modifying getExpression, test your program again.



    Catching errors

    Currently, if the expression has a bad form, your program is killed in a bad way. Try it on a meaningless expression to see what happens.

    Fix that by using a try block. Put the part of your program that gets the expression and prints its value inside the try, as follows.

      try
      {
        part that gets an expression and shows its value
      }
      catch(Error e)
      {
        System.out.println("Bad expression");
      }
    

    Now try a bad expression again. The program should show that the expression is bad and keep running.



    A final error check

    Your program will currently say that the value of expression

      20 30
    

    is 20. It does not realize that it did not read the entire expression. Fix that by modifying main so that, after getting the value of the expression, it gets one more character from the scanner. If that character is the $ character at the end of the string, good. If not, then say that the expression is bad.



    Submitting your work

    After your program works, paste the entire program into the box below.