CS 3723/3731 Programming Languages
Final Exam Answers, Fall 2002

Answers are in boldface!

  1. Formal Grammars and Languages:

    1. Consider the following grammar for Lisp S-expressions.
              S  --->  A    |   '('   T
              T  --->  ')'  |   S     T
              A  --->  'a'  |   'b'   |   'c'
            

      Give the parse tree for the sentence: ( ( a ) ( b c ) a )

      
                  S                     (Nodes "A" are not shown)
                  |
       +----------+--------------+
       |                         |
      "("                        T
                                 |
                 +---------------+------+
                 |                      |
                 S                      T
                 |                      |
          +------+----+           +-----+----------+
          |           |           |                |
         "("          T           S                T
                      |           |                |
                 +----+----+   +--+---+        +---+----+
                 |         |   |      |        |        |
                 S         T  "("     T        S        T
                 |         |          |        |        |
                "a"       ")"     +---+----+  "a"      ")"
                                  |        |
                                  S        T
                                  |        |
                                 "b"    +--+---+
                                        |      |
                                        S      T
                                        |      |
                                       "c"    ")"
        

    2. Consider the ambiguity given by the following fragment of a grammar for C:
             <stmt> ---> if ( <log-expr> ) <stmt>
             <stmt> ---> if ( <log-expr> ) <stmt> else <stmt>
            
      1. What simple rule is used in programming languages to resolve this ambiguity? Just that each "else" is matched with the nearest unmatched "if".
      2. How can one handle of this ambiguity with an actual parser without changing the grammar? (For example, how did you handle the ambiguity in your parser for the course, where the syntax was different but the construct was still an if-then-else with optional else?) Any shift-reduce parser will call for both a "shift" and a "reduce" when it sees an "else". You just throw out the "reduce" and do a "shift" in every case. In recursive descent, the same function handles both "if-then" and "if-then-else". In case of an "else", you go ahead and handle it as part of the "if-then", rather than returning from the funtion after having found and "if-then".

  2. Parsers:

    1. Consider the following grammar:
              S ---> b M b              ("S" is the start symbol)
              M ---> ( L
              M ---> a
              L ---> M a )
      
      Here is a recursive descent parser for this grammar, with two sections of code missing. It also has an initial "P" symbol, and a terminal "$". Fill in the two code sections, including the calls to error in case of an error. (You don't need to know anything about Java to do this problem. If you are not a Java person, just write code as if this were C. The code should be the same in this case.)

    
    /* GrammarTest.java: simple parser
     * grammar:
     *           P ---> S  "$"
     *           S ---> "b"  M  "b" 
     *           M ---> "("  L
     *           M ---> "a"
     *           L ---> M  "a"  ")"
     */
    import java.io.*;
    class GrammarTest {
       private int level = 0;
       private char next;
    
       private void P() {
          scan();	
          S();		
          if (next != '$') error(2);
          else
          System.out.println("Success");
       }		
          			
       private void S() {		
          if (next == 'b') scan();	
          else error(0);
          M();		
          if (next == 'b') scan();	
          else error(1);		
       }		
    
       private void M() {	
          // supply code here
       }		
    
       private void L() {		
          // supply code here      
       }
    
    
     private void scan() {
       do {
           try {
              next = (char)System.in.read();
           } catch (IOException e) {
              System.out.println("Excp");
           }
       } while (Character.isWhitespace(next));
     }
    
     private void error(int n) {
        System.out.println("*** ERROR: " + n);
        System.exit(1);
     }
    
     public static void main(String[] args) {
        GrammarTest grammarTest =
              new GrammarTest();
        grammarTest.P();
     }
    }
    
    
              private void M() {
                 if (next == '(') {
                    scan();
                    L();
                 }
                 else if (next == 'a') scan();
                 else error(3);
              }
    
              private void L() {
                 M();
                 if (next == 'a') scan();
                 else error(4);
                 if (next == ')') scan();
                 else error(5);
              }
        

  3. The Lisp Language:

    1. In each case say what the Lisp interpreter would produce when it evaluates the given input (there are no errors):
      
      > 47 
      47
      > (+ (* 2 3) (/ 8 4)) 
      8
      > (car '((a b) c (d)))
      (A B)
      > (cdr '((a b) c (d)))
      (C (D))
      > (cons '(a) '(b c))
      ((A) B C)
      > (append '(a) '(b c))
      (A B C)
      > (list '(a) '(b c))
      ((A) (B C))
      > (cond ((= 3 4) 47)
              (t 54))
      54
      > ()
      NIL
      > 
      
    2. The following input gives an error when evaluated by Lisp. Explain exactly what the error is (not what the error message is).
      
              > (append (a b c) (d e f))
              >>Error: The function A is undefined
              (Lisp tries to evaluate the arguments first, starting
              with the list (a b c).  This is evaluated by regarding a
              as a function, which is usually would not be.)
            
    3. Write a recursive Lisp function power that will compute an integer raised to a non-negative integer power. In conventional notation (using ^ for power), if the arguments to power are b and e, then

        b ^ e = 1, if e = 0, and
        b ^ e = b * (b ^ (e - 1)), otherwise, that is, if e > 0.

      
      > (defun power (b e) 
         (cond ((= e 0) 1)
               (t (* b (power b (1- e)))) ))
      POWER
      > (power 2 5)
      32
      > (power 2 20)
      1048576
      > (power 2 100)
      1267650600228229401496703205376
      > (power 3 200)
      2656139888758747693387813220357796268292334526533
      94495974574961739092490901302182994384699044001
      

  4. The Postscript Language:

    1. In Postscript, what can be done with the "current path"? That is, what different things is it good for? It can be used for stroking, for filling and for clipping.

    2. Suppose we define a value for a variable r by the Postscript code: /r 8 def

      In a similar way, give the variable r3 the value of r3 (not just 4096, but based on whatever r is). Then give the variable pi the value 3.14159265. Finally, let vol take on the value (4/3)*pi*r3.

      
      /r 8 def
      /r3 r r mul r mul def
      /pi 3.14158265 def
      /vol 4 3 div pi mul r3 mul def
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % Now output the value of vol
      % Tricky stuff -- see Bluebook, pages 71-73
      /volstr 10 string def % string to hold value of vol
      /Times-Roman findfont 40 scalefont setfont
      100 100 moveto
      (vol = ) show
      vol volstr cvs show % convert vol to string and print
      showpage
      

      Here is the result of interpreting this Postscript: vol.

    3. Sketch the path defined by the following Postscript code. (Be careful!)
        newpath
        100 200 moveto
        0 150 rlineto
        200 0 rlineto
        closepath
                 

    4. Use the function
        /figure {
         % five lines from the previous part
        } def
               
      Now give code to fill in the figure with gray shading (your choice of how gray) and then code to draw a line along the path.
      
      /figure {
         newpath
         100 200 moveto
         0 150 rlineto
         200 0 rlineto
         closepath
      } def
      3 setlinewidth
      figure 0.5 setgray fill
      0 setgray
      figure stroke
      showpage
      
      Here is the result of interpreting this Postscript: figure.

    5. Consider the following segment of Postscript code:
        /Times-Roman findfont 80 scalefont setfont
        0 0 moveto
        (A) show
               
      1. What will be printed and where on the page? (Assuming a showpage at the end.) This just prints a large letter "A" in the lower left corner.
      2. Turn this into a Postscript procedure named printit as in the previous numbered part. Now add code to print what will be printed roughly in the middle of the page and upside-down. The following code prints the original letter and the new one:
        
        /printit {
          /Times-Roman findfont 80 scalefont setfont
          0 0 moveto
          (A) show
        } def
        printit
        612 2 div 792 2 div translate
        180 rotate
        printit
        showpage
             
        Here is the result of interpreting this Postscript: Rotated A.

      3. Use the procedure in (b) and a loop to print this object 20 times along a diagonal from the lower left corner of the page to the upper right corner (not a 45 degree angle) Here is one solution:
        
        /iter 20 def
        /xiter 580 iter div def
        /yiter 760 iter div def
        /printit {
          /Times-Roman findfont 80 scalefont setfont
          0 0 moveto
          (A) show
        } def
        0 1 iter 1 sub {
           pop
           printit
           xiter yiter translate
        } for
        
        showpage
           
        Here is the result of interpreting this Postscript: Diagonal A's.

  5. The Prolog Language:

      Consider some simple Prolog facts from the geneology example in class:
        male(neal).
        male(wayne).
        male(nate).
        male(ian).
        female(bethany).
        female(debbie).
        parent(neal, nate).
        parent(neal, ian).
        parent(neal, bethany).
        parent(debbie, nate).
        parent(debbie, ian).
        parent(debbie, bethany).
        married(debbie,neal).
        
    1. Explain in English what the following rule says.
        father(X, Y) :-  male(X), parent(X, Y).
        
      It says that X is the father of Y in case X is male and X is the parent of Y.

      What new facts does this rule provide?

      
      medusa% pl
      ?- consult(exam1).
      exam1 compiled, 0.00 sec, 2,816 bytes.
      
      Yes
      ?- father(X,Y).
      
      X = neal
      Y = nate ;
      
      X = neal
      Y = ian ;
      
      X = neal
      Y = bethany ;
      
      X = albertus
      Y = ralph ;
      
      X = ralph
      Y = neal ;
      
      No
      ?- halt.
      
    2. Consider a few more facts and rules:
        male(albertus).
        male(ralph).
        parent(albertus, ralph).
        parent(ralph, neal).
        ancestor(X,Y)  :-  parent(X,Y).
        ancestor(X, Y) :-  parent(X, Z), ancestor(Z, Y).
        
      Give a new fact about albertus and ian that prolog can deduce. Where does it comes from?

      
      ?- ancestor(albertus, ian).
      
      Yes
      

    3. Write a new rule for daughter, where this means the first argument is the daughter of the second argument. I used:
      
      child(Y, X)    :-  parent(X, Y).
      daughter(Y, X) :-  female(Y),  child(Y, X).
      
      but one could get by with daughter(Y, X) :- female(Y), parent(X, Y).

  6. Topics from C, C++, and Java:

    Generic constructs in C, C++, and Java:

    1. Consider C++ templates. What are its advantage(s) compared with other generic methods (in C and Java)? What are disadvantage(s) of template? Templates are (by far) the most efficient way to do generic constructs. They also provide a simple form for programmers to use in an implementation. However, they are very confusing for implementors to write, downright ugly!

    2. From an object-oriented point of view, what is wrong with using an arbitrary Object in Java and using instanceof to decide what type of object it is? Any code doing something with these objects has to know all the possible available objects, and must have cases to handle them. All this code has to be added to if one adds another type of object. It is possible to write code that works with no changes when one adds another type of object.