CS 3723/3731 Programming Languages
Final Exam, Fall 2002

  1. Formal Grammars and Languages:

    1. Consider the following grammar for Lisp S-expressions. Here "S" is an S-expression, "T" is the tail end of an S-expression, and "A" is an atom, which is just a lower-case letter in this case.
        
              S  --->  A    |   '('   T
              T  --->  ')'  |   S     T
              A  --->  'a'  |   'b'   |   'c'
            

      Give the parse tree for the sentence:

        ( ( a ) ( b c ) a )

    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?
      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?)

  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();
     }
    }
    
  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):
      1. 47
      2. (+ (* 2 3) (/ 8 4))
      3. (car '((a b) c (d)))
      4. (cdr '((a b) c (d)))
      5. (cons '(a) '(b c))
      6. (append '(a) '(b c))
      7. (list '(a) '(b c))
      8. (cond ((= 3 4) 47)
               (t 54))
      9. ()

    2. The following input normally 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))

    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.

      For example, in Lisp notation, (power 2 5) is 32. (Of course, Lisp has a built-in "raise-to-a-power" function called expt, but you are not to use that.)

  4. The Postscript Language:

    1. In Postscript, what can be done with the "current path"? That is, what different things is it good for?

    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.

    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.

    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.)
      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.
      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)

  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).
        
      What new facts does this rule provide?

    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?

    3. Write a new rule for daughter, where this means the first argument is the daughter of the second argument.

  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?

    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?