COP 4020 Programming Assignment 5: Calculator with Assignment

Educational Objectives: After completing this assignment, the student should be able to do the following:

Operational Objectives: Implement a calculator with assignment in Java using an L-attributed grammar

Deliverables: One file Calc5.java

  1. Consider the following augmented LL(1) grammar for an expression language:

    <expr>         -> <term> <term_tail>           term_tail.subtotal := term.value;
                                                   expr.value := term_tail.value
    <term>         -> <factor> <factor_tail>       factor_tail.subtotal := factor.value;
                                                   term.value := factor_tail.value
    <term_tail1>   -> '+' <term> <term_tail2>      term_tail2.subtotal :=
                                                             term_tail1.subtotal+term.value;
                                                   term_tail1.value := term_tail2.value
                    | '-' <term> <term_tail2>      term_tail2.subtotal :=
                                                             term_tail1.subtotal-term.value;
                                                   term_tail1.value := term_tail2.value
                    | empty                        term_tail1.value := term_tail1.subtotal
    <factor1>      -> '(' <expr> ')'               factor1.value := expr.value
                    | '-' <factor2>                factor1.value := -factor2.value
                    | number                       factor1.value := number
    <factor_tail1> -> '*' <factor> <factor_tail2>  factor_tail2.subtotal :=
                                                             factor_tail1.subtotal*factor.value;
                                                   factor_tail1.value := factor_tail2.value
                    | '/' <factor> <factor_tail2>  factor_tail2.subtotal :=
                                                             factor_tail1.subtotal/factor.value;
                                                   factor_tail1.value := factor_tail2.value
                    | empty                        factor_tail1.value := factor_tail1.subtotal
    

    Note: the indexing (1 and 2) used with nonterminals, such as <factor1> and <factor2>, is only relevant to the semantic rules to identify the specific occurrences of the nonterminals in a production. (See text.)

    Draw the decorated parse tree for -2*3+1 that shows the attributes and their values.

  2. The following calculator Java program implements the attribute grammar shown above to calculate the value of an expression. To this end, the synthesized value attributes are returned as integer values from the methods that correspond to nonterminals. Inherited subtotal attributes are passed to the methods as arguments:

    /* Calc.java
       Implementes a parser and calculator for simple expressions
       Uses java.io.StreamTokenizer and recursive descent parsing
    	
       Compile:
       javac Calc.java
    
       Execute:
       java Calc
       or:
       java Calc <filename>
    */
    import java.io.*;
    public class Calc
    {
      private static StreamTokenizer tokens;
      private static int token;
      public static void main(String argv[]) throws IOException
      {
        InputStreamReader reader;
        if (argv.length > 0)
          reader = new InputStreamReader(new FileInputStream(argv[0]));
        else
          reader = new InputStreamReader(System.in);
        // create the tokenizer:
        tokens = new StreamTokenizer(reader);
        tokens.ordinaryChar('.');
        tokens.ordinaryChar('-');
        tokens.ordinaryChar('/');
        // advance to the first token on the input:
        getToken();
        // parse expression and get calculated value:
        int value = expr();
        // check if expression ends with ';' and print value
        if (token == (int)';')
          System.out.println("Value = " + value);
        else
          System.out.println("Syntax error");
      }
      // getToken - advance to the next token on the input
      private static void getToken() throws IOException
      {
        token = tokens.nextToken();
      }
      // expr - parse <expr> -> <term> <term_tail>
      private static int expr() throws IOException
      {
        int subtotal = term();
        return term_tail(subtotal);
      }
      // term - parse <term> -> <factor> <factor_tail>
      private static int term() throws IOException
      {
        int subtotal = factor();
        return factor_tail(subtotal);
      }
      // term_tail - parse <term_tail> -> <add_op> <term> <term_tail> | empty
      private static int term_tail(int subtotal) throws IOException
      {
        if (token == (int)'+')
        {
          getToken();
          int termvalue = term();
          return term_tail(subtotal + termvalue);
        }
        else if (token == (int)'-')
        {
          getToken();
          int termvalue = term();
          return term_tail(subtotal - termvalue);
        }
        else
          return subtotal;
      }
      // factor - parse <factor> -> '(' <expr> ')' | '-' <expr> | identifier | number
      private static int factor() throws IOException
      {
        if (token == (int)'(')
        {
          getToken();
          int value = expr();
          if (token == (int)')')
            getToken();
          else
            System.out.println("closing ')' expected");
          return value;
        }
        else if (token == (int)'-')
        {
          getToken();
          return -factor();
        }
        else if (token == tokens.TT_WORD)
        {
          getToken();
          // ignore variable names
          return 0;
        }
        else if (token == tokens.TT_NUMBER)
        {
          getToken();
          return (int)tokens.nval;
        }
        else
        {
          System.out.println("factor expected");
          return 0;
        }
      }
      // factor_tail - parse <factor_tail> -> <mult_op> <factor> <factor_tail> | empty
      private static int factor_tail(int subtotal) throws IOException
      {
        if (token == (int)'*')
        {
          getToken();
          int factorvalue = factor();
          return factor_tail(subtotal * factorvalue);
        }
        else if (token == (int)'/')
        {
          getToken();
          int factorvalue = factor();
          return factor_tail(subtotal / factorvalue);
        }
        else
          return subtotal;
      }
    }
    

    Copy this example Calc.java program from ~cop4020p/examples/, and compile and run it:

    javac Calc.java
    java Calc
    

    Explain why the input 1/2; to this program produces the value 0. What are the relevant parts of the program involved in computing this result?

  3. Extend the attribute grammar with two new productions and two new attributes for all nonterminals:

    1. The in inherited attribute is a symbol table with identifier-value bindings that defines the bindings of identifiers in the scope (context) in which (part of) the expression is evaluated,
    2. The out synthesized attribute is a symbol table with identifier-value bindings that holds the in bindings plus the new bindings introduced by (part of) the expression as explained below.

    The two new productions with corresponding semantic rules are as follows:

    <expr1>   -> 'let' identifier '=' <expr2> expr2.in := expr1.in;
                                              expr1.value := expr2.value
                                              expr1.out := expr2.out.put(identifier=expr2.value)
               | <term> <term_tail>           term.in := expr1.in;
                                              term_tail.in := term.out;
                                              term_tail.subtotal := term.value;
                                              expr1.value := term_tail.value;
                                              expr1.out := term_tail.out
    <factor1> -> '(' <expr> ')'               expr.in := factor1.in;
                                              factor1.value := expr.value
                                              factor1.out := expr.out
               | '-' <factor2>                factor2.in := factor1.in;
                                              factor1.value := -factor2.value;
                                              factor1.out := factor2.out
               | identifier                   factor1.value = factor1.in.get(identifier)
               | number                       factor1.value := number;
                                              factor1.out := factor1.in
    

    The first production introduces an assignment construct as an expression, similar to the C/C++ assignment which can also be used within an expression, as in this example:

    (let x = 3) + x;
    Value = 6
    

    The semantic rule expr2.in := expr1.in copies the symbol table of the context in which expr1 is evaluated to the context of expr2. The evaluation of expr2 may change the symbol table and the table is copied to expr1 with the semantic rule expr1.out := expr2.out. For this part of the assignment, you have to change the semantic rules of all other productions in the grammar to include assignments for the in and out attributes to pass the symbol table. Write down the grammar with these new semantic rules.

  4. Implement the two new productions and semantic rules in an updated Calc.java program.

    To implement a symbol table with identifier-value bindings, you can use the Java java.util.Hashtable class as follows:

    import java.util.*;
    ...
    public class Calc
    {
      ...
      public static void main(String argv[]) throws IOException
      {
        ...
        Hashtable<String,Integer> exprin = new Hashtable<String,Integer>();
        Hashtable<String,Integer> exprout;
        ...
        int value = expr(exprin, exprout);
        ...
        private static int expr
          (Hashtable<String,Integer> exprin, Hashtable<String,Integer> exprout) throws IOException
        {
          if (token == tokens.TT_WORD && tokens.sval.equals("let"))
          {
            getToken(); // advance to identifier
            String id = tokens.sval;
            getToken(); // advance to '='
            getToken(); // advance to <expr>
            int value = expr(exprin, exprout);
            exprout.put(id, new Integer(value));
          }
          else
          {
            int subtotal = term(exprin, termout);
            return term_tail(subtotal, termout, exprout);
          }
        }
        private static int factor
          (Hashtable<String,Integer> factorin, Hashtable<String,Integer> factorout) throws IOException
        {
          ...
          else if (token == tokens.TT_WORD)
          {
            String id = tokens.sval;
            getToken();
            factorout = factorin;
            return ((Integer)factorin.get(id)).intValue();
          }
          ...
    

    The put method puts a key and value in the hashtable, where the value must be a class instance so an Integer instance is created. The get method returns the value of a key. The intValue method of Integer class returns an int. Test your new Calc.java application. For example:

    let x = 1;
    Value = 1
    
    (let x = 1) + x;
    Value = 2
    
    (let a = 2) + 3 * a;
    Value = 8
    
    1 + (let a = (let b = 1) + b) + a;
    Value = 5
    

Turn in this assignment as a working Java program named "Calc5.java", using the submit script "pr5submit.sh". Your answers for the non-programming questions should be inserted in the documentation at the top of your file. Use plain text to draw trees and write grammars as required.

Suggestions on drawing trees. There are (at least) two basic ways to illustrate trees using ascii text. The first is "pyramidal":


                                  <expr>(-3)
                        --------------------------------
                       /                                \
                  <term>(-5)                             <term_tail1>[-5](-3)
                 --------                               -------------
                /        \                             /     |       \

           .................................................................

      Note: [] represents inherited attribute values
            () represents synthesized attribute values

A second way to represent the same tree is a squared off version:

<expr>(-3)
----------------------------------------------------
    |                                          |
<term>(-5)                             <term_tail1>[-5](-3)
---------------                        ----------------------------
    |        |                                 |        |        |

 .....................................................................

      Note: [] represents inherited attribute values
            () represents synthesized attribute values

The latter may be easier to use, especially for decorated parse trees where a lot of information is displayed for each node. Note that in either case we are using [] to enclose inherited attribute values and () to enclose synthesized attribute values.