RHODES UNIVERSITY

November Examinations - 1999 - Solutions


Computer Science 301 - Paper 1 - Solutions

Examiners:                                          Time 3 hours
   Prof P.D. Terry                                  Marks 180
   Prof D. Kourie                                   Pages 8 (please check!)

Answer all questions. Answers may be written in any medium except red ink.

(For the benefit of future readers of this paper, various free information was made available to the students 24 hours before the formal examination. This included the full text of Section B. During the examination, candidates were given machine executable versions of the Coco/R compiler generator, and access to a computer and machine readable copies of the questions.)


Section A [ 105 marks ]

1 (a) Explain what is meant by the FIRST and FOLLOW sets as applied to grammar analysis, and give a concise (mathematical) definition of set membership for each of these. [4]

(b) State the rules that a grammar must obey for it to be LL(1). Do not write vague English; describe the rules in concise mathematics. [6]

Standard bookwork

2 EBNF and BNF are notations for defining productions in context free grammars. In EBNF, as you should know, "repetition" and "optionality" may be expressed using metabracket notation, whereas in BNF these features are expressed using recursion and selection. The Cocol grammar below effectively uses EBNF notation to describe the form EBNF productions can take.

   COMPILER EBNF
   /* Describe simple EBNF productions, using EBNF conventions */

   CHARACTERS
     letter   = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".
     lowline  = "_".
     digit    = "0123456789".
     noquote1 = ANY - "'".
     noquote2 = ANY - '"'.

   IGNORE  CHR(9) .. CHR(13)
   COMMENTS FROM "(*" TO "*)"  NESTED

   TOKENS
     nonterminal = letter {letter | lowline | digit} .
     terminal    = "'" noquote1 { noquote1 } "'" | '"' noquote2 { noquote2 } '"' .
   PRODUCTIONS
      EBNF       = { Production } EOF .
      Production = nonterminal "=" Expression "." .
      Expression = Term { "|" Term } .
      Term       = [ Factor { Factor } ] .
      Factor     =   nonterminal | terminal | "(" Expression ")"
                   | "[" Expression "]" | "{" Expression "}" .
   END EBNF.

(a) What do the acronyms "EBNF" and "BNF" stand for? [2]

BNF = Backus Naur Form and EBNF = Extended Backus-Naur Form.

(b) How would you change the PRODUCTIONS section of the Cocol specification so that it used EBNF conventions, but described only the form of BNF productions? [4]


       EBNF       = { Production } EOF .
       Production = nonterminal "=" Expression "." .
       Expression = Term { "|" Term } .
       Term       = [ Factor { Factor } ] .
       Factor     = nonterminal | terminal | "(" Expression ")" .

(c) How would you change the PRODUCTIONS section of the Cocol specification so that it only used BNF conventions, but described the form of EBNF productions? [6]


       EBNF          = Productions EOF .
       Productions   = Production Productions | e .
       Production    = nonterminal "=" Expression "." .
       Expression    = Term RestOfTerms .
       RestOfTerms   = "|" Term RestOfTerms | e .
       Term          = Factors | e .
       Factors       = Factor RestOfFactors .
       RestOfFactors = Factor RestOfFactors | e .
       Factor        =   nonterminal | terminal | "(" Expression ")" .
                       | "[" Expression "]" | "{" Expression "}" .

(d) Do the productions that you have written for (c) result in an LL(1) compliant grammar? Give reasons for your answer. [4]

The ones above do; others might not.

3 Palindromes are strings that read the same from either end, such as "abba", "aha", "noon" or "wow". Presumably one can write grammars to describe palindromes. Here are a few attempts to write productions to do this for palindromes that consist only of the letters "a" and "b".

       Grammar 1:
           A = "a" A "a" | "b" A "b" .

       Grammar 2:
          A = "a" A "a" | "b" A "b"  | "a" | "b" .

       Grammar 3:
          A = B A B | e .
          B = "a" | "b" .

Some (or maybe all) of these grammars do not describe the language of such palindromes adequately. Identify which do not, giving reasons for your answer, and present a set of productions that you believe is correct. Is it possible to find an LL(1) compliant grammar for this language. If not, why not? [12]

None work. Grammar 1 does not "terminate". Grammar 2 is non-LL(1) and does not allow the empty case, so it only describes palindromes of odd length. Grammar 3 does not ensure that the first and last letters match, nor does it allow palindromes of odd length. A better grammar would be:

       A = "a" A "a" | "b" A "b"  | "a" | "b" | e  .

One cannot find an LL(1) grammar for the same reason that one cannot tell, when one walks into a forest, when one has reached half way just by looking at the next tree! Or, as one astute student observed, one needs a grammar with at least the alternatives of Grammar 1

           A = "a" A "a" | "b" A "b" | other alternatives.
where the other alternatives, needed to ensure that the grammar terminated, would inevitably ensure that Rule 2 was broken, if not Rule 1. Another student suggested an intriguing variation on Grammar 2 to fix the obvious LL(1) problem
           A = "a" [ A "a"] | "b" [ A "b"] .
which showed that he/she had learned how to refactor grammars. But this one still breaks Rule 2, and still does not allow even-length palindromes.

Incidentally, the simple strings "a" and "b" by themselves are perfectly valid, if rather trivial palindromes.

4 Here is a true story: A few weeks ago, one of the many Coco/R users in the world emailed me with a suggestion. He was unimpressed with the way in which one had to write specifications like

     CHARACTERS
       letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".
       digit = "0123456789".

and suggested that we add a feature to allow (as an alternative)

     CHARACTERS
       letter = CHARS("A" .. "Z") + CHARS("a" .. "z") .
       digit = CHARS("0" .. "9") .

Since I have access to the sources of Coco, which is a self-compiling system, I should be able to bootstrap the new system quite easily, (by adding to one of the productions in the file CR.ATG).

(a) What do you understand by the term self-compiling compiler? [2]

A self-compiling compiler is essentially written in the language it is designed to compile.

(b) Draw T-diagrams showing what I should do to produce the new Coco/R executable for distribution on our web page. [8]

First modify the atribute grammar for Coco/R and pass it through the original system:

      ----------------------------          ----------------------------
      |        CRNEW.ATG         |          |        CRNEW.CPP         |
      |  ATG                C++  |          |  ATG                 C++ |
      |                          |          |                          |
      ---------          ----------------------------          ---------
              |          |         CROLD.EXE        |          |
              |    ATG   |   ATG  -------->     C++ |     C++  |
              |          |                          |          |
              --------------------          --------------------
                                 |          |
                                 |   8086   |
                                 |          |
                                 ------------
                                 |          |
                                 |   8086   |
                                 |          |
                                 ------------
Then use the C++ compiler to compile those sources:
      ----------------------------          ----------------------------
      |        CRNEW.CPP         |          |         CRNEW.EXE        |
      |  ATG                CPP  |          |  ATG                 CPP |
      |                          |          |                          |
      ---------          ----------------------------          ---------
              |          |          BCC.EXE         |          |
              |    C++   |   C++  -------->    EXE  |    EXE   |
              |          |    (used as compiler)    |          |
              --------------------          --------------------
                                 |          |
                                 |   8086   |
                                 |          |
                                 ------------
                                 |          |
                                 |   8086   |
                                 |          |
                                 ------------

5. Pragmas and comments often appear in source code submitted to compilers. What property do pragmas and comments have in common, and what is the semantic difference between them? [5]

Neither pragmas not comments form part of the dynamic semantics, and can be ignored in that respect. They usually can also appear in source anywhere that white space can be, that is, between any two tokens. But pragmas can choose compiler options, as you should have realised from using them in the practical course.

6. What do you understand by the term short circuit semantics as applied to the evaluation of Boolean expressions? Illustrate your answer by means of two specimens of code that might correspond to a simple Boolean expression of your choice. [5]

Standard bookwork sort of discussion expected. Frankly I ws amazed (and alarmed) that only about 10% of the class seemed to have ANY idea what short-circuit semantics were - surely you have been made aware of this when you were taught Basic, C++, compilers ...? An example might be

          WHILE  (1 < P)  AND  (P < 9)  DO   P := P + Q  END

          L0      if 1 < P goto L1   ; Short circuit version
                  goto L3
          L1      if P < 9 goto L2
                  goto L3
          L2      P := P + Q
                  goto L0
          L3      continue


          L0      T1 := 1 < P        ; standard Boolean operator approach
                  T2 := P < 9
                  if T1 and T2 goto L1
                  goto L2
          L1      P := P + Q
                  goto L0
          L2      continue

7. Consider the grammar below for a (familiar) tiny C-like subset language, and the example of a program in that language.

   COMPILER ExtaC

   IGNORE CHR(9) .. CHR(13)
   COMMENTS FROM "/*" TO "*/"

   CHARACTERS
     letter     = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" .
     digit      = "0123456789" .

   TOKENS
     identifier = letter { letter | digit } .
     number     = digit { digit } .

   PRODUCTIONS
     ExtaC        = { Library } { Declaration } .
     Library      = "#include" "<" identifier ">" .
     Declaration  = Variables | Constant | Function .
     Variables    = Type identifier { "," identifier } ";" .
     Type         = "int" | "bool" | "char" .
     Constant     = "const" [ Type ] identifier "=" Expression ";" .
     Function     = "void" identifier "(" FormalParams ")"
                    (   ";"                     /* prototype */
                      | "{" { Statement } "}"   /* definition */
                    ) .
     FormalParams = [ Type identifier { "," Type identifier } | "void" ] .
     Statement    = Declaration | AssignOrCall .
     AssignOrCall = identifier ( "=" Expression | "(" ActualParams ")" ) ";" .
     ActualParams = [ Expression { "," Expression } ] .
     Expression   = Term { AddOp Term } .
     Term         = Factor { MulOp Factor } .
     Factor       = [ "-" | "!" ] ( identifier | number | "(" Expression ")" ) .
     AddOp        = "+" | "-" | "||" .
     MulOp        = "*" | "/" | "&&" .
   END ExtaC.

(a) The grammar for ExtaC is "context free". What do you understand by the term "context free" as applied to compiler theory? (Give a concise, mathematical definition, not a mini-essay!) [3]

Standard bookwork.

(b) Show that you understand what is meant by the concept of "scope" by indicating clearly which identifiers (give their names and their types) you understand to be "in scope" at the point marked by the comment /* what is in scope at this point? */ in the specimen program below. [4]

   /* Silly example of ExtaC - not claimed to have any real dynamic semantics */

      char x, y, z;
      bool a, b;

      void One (void);  /* prototype */

      void Two (void) {
        const int z = -4;
        bool y;
        int x;
                        /* what is in scope at this point? */
        One();
        x = 40 / 5;
      }

      int p, q, r;
      const bool EasyExam = true || false;

      void One (void) {
                        /* discuss run-time memory allocation when this point is
                           reached for the second time */
        Two();
      }

      void main (void) {
        char p, q, r;
        p = q;
        One();
      }

bool a, b, y; void One, Two; int x, z;

(c) Detach the diagram provided, and by completing it and writing appropriate notes, discuss how the run-time memory management could be handled for programs of this sort. In the course of your discussion elaborate on the ideas of stack frames, dynamic links, static links, and stack and base pointers, and illustrate what the layout of memory might be when the program below reaches the point indicated for the second time (the program is recursive, and would, of course, never terminate, but ignore that for the purposes of this discussion). [20]

Standard sort of discussion expected, as found in Chapter 16. Answers received were very disappointing; clarly this section had not been understood or studied properly. Only a very small number of people appreciate exactly what the global variables were, and that main/TT> is itself a function


Memory allocation immediately after main starts execution
                                                              SP                       BP
                                                              |                        .------------------->----------------------.
                                                              |    main                |                  Globals                 |
.--------------------------------------------------------------------------------------+------------------------------------------.
|    |    |    |    |    |    |    |    |    |    |    |    | r  | q  | p  | RA | SL | DL | r  | q  | p  | b  | a  | z  | y  | x  |
|    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
`-----------------------------------------------------------------------------+----.----------------------------------------------'
                                                                              |    |                                              |
                                                                          <---'    `----------------------->----------------------'

Memory allocation after One starts execution
                                               SP       BP
                                                |       |.-------------->-------------. .------------------>----------------------.
                                                | One   ||         main               | |                 Globals                 |
.---------------------------------------------------------------------------------------------------------------------------------.
|    |    |    |    |    |    |    |    |    | RA | SL | DL | r  | q  | p  | RA | SL | DL | r  | q  | p  | b  | a  | z  | y  | x  |
|    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
`-----------------------------------------------+----.------------------------+----.----------------------------------------------'
                                            <---'    |                    <---'    |                                              |
                                                      `----------------->----------`----------------------->----------------------'

Memory allocation after Two starts execution

                      SP                 BP
                       |                 |.---->--------  .------------->-------------. .------------------>----------------------.
                       |   Two           ||       One   | |        main               | |                 Globals                 |
.---------------------------------------------------------------------------------------------------------------------------------.
|    |    |    |    | x  | y  | RA | SL | DL | RA | SL | DL | r  | q  | p  | RA | SL | DL | r  | q  | p  | b  | a  | z  | y  | x  |
|    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
`--------------------------------+----.---------+----.------------------------+----.----------------------------------------------'
                             <---'    |     <---'    |                    <---'    |                                              |
                                      `-------->-----`------------------>----------`----------------------->----------------------'

Memory allocation after One is called the second time

        SP       BP
        |        |.--------->------------. .----->------  .------------->-------------. .------------------>----------------------.
        | One    ||        Two           | |      One   | |        main               | |                 Globals                 |
.---------------------------------------------------------------------------------------------------------------------------------.
|    | RA | SL | DL | x  | y  | RA | SL | DL | RA | SL | DL | r  | q  | p  | RA | SL | DL | r  | q  | p  | b  | a  | z  | y  | x  |
|    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
`-------+----.-------------------+----.---------+----.------------------------+----.----------------------------------------------'
    <---'    |               <---'    |     <---'    |                    <---'    |                                              |
             `-------------->---------`-------->-----`------------------>----------`----------------------->----------------------'

8. Brinch Hansen introduced an extended form of the WHILE loop into his experimental language Edison:

        WhileStatement  =  "while" Expression "do" Statement
                           { "else" Expression "do" Statement } .

The dynamic semantics of this peculiar construct are that Expressions are evaluated one at a time in the order written until one is found to be true, when the corresponding Statement is executed, after which the process of evaluating conditions is repeated. If no Expression is true, the loop terminates.

Assuming that you want to incorporate this into a language like ExtaC, and to target a stack machine similar to the one you have met on the course, how would you attribute the production to handle code generation? [10]

We had done this in a tutorial. The C++ version is shown; the Pascal one is essentially identical.

  WhileStatement
  =                        (. CGEN_labels startloop, testlabel, dummylabel; .)
     "WHILE"               (. CGen->storelabel(startloop); .)
     Expression "DO"       (. CGen->jumponfalse(testlabel, CGen->undefined); .)
     Statement             (. CGen->jump(dummylabel, startloop); .)
     {                     (. CGen->backpatch(testlabel); .)
     "ELSE Expression "DO" (. CGen->jumponfalse(testlabel, CGen->undefined); .)
     Statement             (. CGen->jump(dummylabel, startloop); .)
     }                     (. CGen->backpatch(testlabel); .)
     "END" .

9. In the Clang compiler with which you are familiar, you will recall that the language allowed for constant declarations:

        ConstDeclarations = "CONST" OneConst { OneConst } .
        OneConst          = Ident "=" ( Number | "TRUE" | "FALSE" ) ";" .

(The attributed grammar that processed these is presented below to refresh your memory).

Suppose we wished to extend the language so that CONST could also be used to introduce synonyms (alternative names) for previously-declared variables and constants, for example

          CONST
            Max = 10;
          VAR
            X, Y[10];
          CONST               (* this time to act as a renaming facility *)
            MyMax = Max;      (* MyMax and Max are really the same constant *)
            YourX = X;        (* YourX is another name for X *)
            List  = Y;        (* List is another name for Y *)
          BEGIN
            X := Max + MyMax; (* X would be assigned the value 10 + 10 *)
            List[3] := Y[4];  (* replace the 3rd element of the array by the 4th element *)

Alter the specification and attributes of the OneConst production to achieve this. [10]

The simplest solution is something on the lines of the following (again, the Pascal version would be essentially the same). Many people had appreciated that a search of the table would be needed; rather fewer had seen that this problem is really very simple - all one needs to do is extract the "old" entry, change the lookup key to the new one, and then make another entry. So it can be done in about three lines (behind every apparently impossible PDT problem is a very simple solution, he reminds you again!)

  OneConst
  =                           (. TABLE_entries entry;
                                 TABLE_alfa oldName, newName; .)
     Ident<newName>           (. entry.idclass = TABLE_consts; .)
     WEAK "="
     ( Number<entry.c.value>  (. entry.type = TABLE_ints; .)
       | "TRUE"               (. entry.type = TABLE_bools; entry.c.value = 1; .)
       | "FALSE"              (. entry.type = TABLE_bools; entry.c.value = 0; .)
       | Ident<oldName>       (. Table->search(oldName, entry, found);
                                 if (!found) SemError(201); .)

     ) ";"                    (. strcpy(entry.name, newName);
                                 Table->enter(entry); .) .

Extracts from the Clang compiler specification relating to the declaration of constants.

C++ version:

Grammar: 

  ConstDeclarations =  "CONST" OneConst { OneConst } .
  OneConst
  =                           (. TABLE_entries entry; .)
     Ident<entry.name>        (. entry.idclass = TABLE_consts; .)
     WEAK "="
     ( Number<entry.c.value>  (. entry.type = TABLE_ints; .)
       | "TRUE"               (. entry.type = TABLE_bools; entry.c.value = 1; .)
       | "FALSE"              (. entry.type = TABLE_bools; entry.c.value = 0; .)
     ) ";"                    (. Table->enter(entry); .) .


Definitions of entries in the symbol table handler: 

  const int TABLE_alfalength = 15; // maximum length of identifiers
  typedef char TABLE_alfa[TABLE_alfalength + 1];

  enum TABLE_idclasses { TABLE_consts, TABLE_vars, TABLE_progs };
  enum TABLE_types {TABLE_none, TABLE_ints, TABLE_chars, TABLE_bools };

  struct TABLE_entries {
    TABLE_alfa name;             // identifier
    TABLE_idclasses idclass;     // class
    TABLE_types type;
    union {
      struct {
        int value;
      } c;                       // constants
      struct {
        int size, offset;
        bool scalar;
      } v;                       // variables
    };
  };

Pascal version

Grammar: 

  ConstDeclarations =  "CONST" OneConst { OneConst } .
  OneConst
                              (. VAR Entry : TABLE.ENTRIES; .)
  =  Ident<Entry.Name>        (. Entry.IDClass := Consts  .)
     WEAK "="
     ( Number<Entry.Value>    (. Entry.Type := ints; .)
       | "TRUE"               (. Entry.Type := bools; Entry.Value := 1; .)
       | "FALSE"              (. Entry.Type := bools; Entry.Value := 0; .)
     ) ";"                    (. TABLE.Enter(Entry) .) .


Definitions of entries in the symbol table handler: 

  TYPE
    ALFA      = STRING[AlfaLength];
    IDCLASSES = (Consts, Vars, Progs);
    TYPES     = (none, ints, chars, bools);
    ENTRIES   =
      RECORD
        Name : ALFA;
        Tipe : TYPES;
        CASE IDClass : IDCLASSES OF
           Consts :
            ( Value : INTEGER );
           Vars :
            ( Size, Offset : INTEGER;
              Scalar : BOOLEAN )
      END;


Section B [ 75 marks ]

Please note that there is no obligation to produce a machine readable solution for this section. Coco/R and other files are provided so that you can enhance, refine, or test your solution if you desire. If you choose to produce a machine readable solution, you should create a working directory, unpack EXAMP.ZIP (Pascal) or EXAMC.ZIP (C++), modify any files that you like, and then copy all the files back to the blank diskette that will be provided.

We have a minor crisis in the Department. Because the experienced staff member who normally gives the Boolean Algebra section of our course has just been taken ill, we have had to ask a new lecturer to take over the course at very short notice, and he needs help ...

We assume that you are familiar with the basic concepts of Boolean algebra, which is defined on a set of operands, say B = {a, b, ... }, two binary infix operators . and + (AND and OR), and a unary postfix operator ' (NOT) .

As you will recall, these operators have a precedence ordering. NOT takes precedence over AND, which takes precedence over OR. This means that an expression written without parentheses, such as a.b + c (or, equivalently, a AND b OR c) means the same as the parenthesized expression (a.b) + c, and does not mean the same as the parenthesized expression a.(b + c). Sometimes the AND operation becomes implicit - as in simple algebra where "a b" can be read as "a times b", so in Boolean algebra "a b" can be read as "a AND b"

Other symbols are sometimes used for the operators. (Expressed as an apostrophe ' , the NOT operator is one of the very few mathematical operators that is written after its operand, as what is called a postfix operator. Another example of a postfix operator is the "factorial" one, as in n!). In computer language terms it is usual to find prefix operators preferred to postfix ones, and to avoid the use of . and +, which tend to get confused with decimal points and addition signs. Hence one finds the use of the full words (as in Pascal and Modula-2) or the use of other symbols entirely, such as & to mean AND, | to mean OR, and ! or ~ to mean NOT.

Here is the same Boolean expression written in several different ways, using these various notations, with parentheses used in the expressions on the right to make the precedence quite explicit:

         A AND B OR NOT C AND D  (A AND B) OR ((NOT C) AND D)
         a.b + c'.d              (a.b) + (c'.d)
         a b + c' d              (a b) + (c' d)
         a & b | ~c & d          (a & b) | ((~c) & d)
         a & b | !c & d          (a & b) | ((!c) & d)

When beginners are taught Boolean algebra and combinational circuits in introductory Computer Science courses, they are often introduced to the concept of a truth table, like those given below (sometimes these tables are expressed in terms of 0's and 1's; sometimes in terms of TRUE and FALSE).

             .----------------.
             |   X    | NOT X |
             |--------+-------|
             |  FALSE |  TRUE |
             |   TRUE | FALSE |
             `----------------'

        .---------------------------.
        |   ONE   TWO | ONE XOR TWO |
        |-------------+-------------|
        |    0     0  |     0       |
        |    0     1  |     1       |
        |    1     0  |     1       |
        |    1     1  |     0       |
        `---------------------------'

Such tables are easy enough to derive by hand, though it is tedious to do so for expressions that involve more than two inputs, and the process is error prone. On Monday the new lecturer is scheduled to teach students about truth tables, and he is desperately looking for a tool that will help him produce these in a hurry for arbitrarily complex expressions. He wants to be able to feed in a list of (independent) functions, for example

   BOOLEAN F(x) = x';
   BOOLEAN f(x, y) = x | y;
   xor(one, two) = one & two' or one' & two;
   CanReturn(S1, S2, S3, Excluded) = (S1 S2 OR S2 S3 + S3 S1) AND ~ Excluded;
   BOOLEAN Equality(x) = x ' ' & ! (NOT x);
   NUMERIC Silly(Twit) = 1 . 0' + Twit;
   BOOLEAN Silly(Twit) = TRUE AND NOT FALSE OR Twit

each one defining some function of its explicit arguments, and separated one from the next by the traditional semicolons. For each function a table should be produced showing the value of the function for each combination of the possible values of its parameters. If present, the keyword BOOLEAN indicates that the table should be produced in terms of combinations of TRUE and FALSE; the absence of a keyword, or the explicit keyword NUMERIC indicates that the table should be produced in terms of combinations of 0's and 1's. Notice that the expressions defining the functions:

Of course, as a senior undergraduate well versed in the use of compiler generating tools such as Coco/R, and in spite of these incredible time constraints, you should have little trouble in producing a system for him to use by Saturday evening, well in advance of Monday morning. After all, you have learned to generate code for simple stack machines, and still have that copy of a Clang compiler to turn to for useful ideas! So take up the challenge.

Being human like the rest of us, the lecturer wants a tool that will check his expressions for syntactic and semantic correctness. He is prepared to accept a fairly simple-minded approach to generating the results - it will suffice to have a system where these are simply directed to the standard output file. And, since he has a licensed copy of both the C++ and Pascal versions of Coco/R, he is prepared to accept submissions in the form of a Cocol attribute grammar using either language, plus any support modules needed, either on diskette or simply written on paper.

This can probably be done in many ways. I derived two solutions, one of which simply used the stack machine as supplied, and a a better one which extended this machine. Here are solutions for the simpler version. The fundamental idea is simply to:

As it happened, Practical 16, task 6 had been cunningly designed in anticipation of this exam question. In that Practical you had been given code like

   PROGRAM DeMorganDemo;
   (* P.D. Terry, Rhodes University, 1999 *)

     BOOL
       X, Y;

     BEGIN
       Write('   X     Y      (X.Y)'' X'' + Y''');
       X := FALSE;
       REPEAT
         Y := FALSE;
         REPEAT
           Write(X, Y, NOT (X AND Y), NOT X OR NOT Y);
           Y := NOT Y;
         UNTIL Y = FALSE (* again *)
         X := NOT X
       UNTIL X = FALSE  (* again *)
     END.

One has the same sort of "prologue" and "epilogue" for setting up a loop to cycle through the values for each variable, and in the innermost body there is simply the important code to evaluate the expression, and then write out its value. One has to be careful to set up the "labels" correctly, and to set up the loops correctly (take a careful look at the attributes used in the Function production; not many got these exactly right).

To be fair, the standard "writestring" system does not produce pretty output across the top of the table. The version of TRUTH.EXE you had been given in the exam kit used an extended version of the PRS opcode to achieve a better effect. Every year some students throw up ideas that are at least as good if not better than my own; I was delighted to see that some students had really imaginative ideas about how to format these names that I had not thought of using.

Overall, though, the solutions received were of rather poor quality, other than about half a dozen who had clearly seen almost exactly how to achieve what was required, and produced superb results, with only the sort of silly mistakes that one must expect under pressure. Given that the grammar itself had appeared in Quiz 25, it was disappointing that many people did not even have this correct, or had got virtually no further.

C++ Solution

   COMPILER Truth $XNC
   /* Truth Table grammar
      This solution uses the Stack machine, table handler and code generator of
      the Clang compiler.  It generates simple repeat loops automagically through
      calls to the internal routines Prolog and Epilog before evaluating the rhs
      of each expression.  Have a look at Prac 16, Task 6 and Quiz 25.  Quiz 25
      actually gave you most of the grammar, while Prac 16 shows how to set up
      the loops.  Ah well, next time you'll study the solutions!
      P.D. Terry, Rhodes University, 1999 */

   #include "table.h"
   #include "report.h"
   #include "cgen.h"

   extern TABLE  *Table;
   extern CGEN   *CGen;
   extern REPORT *Report;  // needed for debugging pragma

   bool WriteAsBoolean;
   CGEN_labels LabelList[100];

   void Prolog1 (int v, CGEN_labels &l)
   {
     CGen->stackaddress(v);
     CGen->stackconstant(0);
     CGen->assign();
     CGen->storelabel(l);
   }

   void Prolog2 (int v)
   {
     CGen->stackaddress(v);
     CGen->dereference();
     CGen->stackconstant(6);  /* default format for PRN or PRB */
     if (WriteAsBoolean) CGen->writeboolean(); else CGen->writevalue();
   }

   void Epilogue (int v, CGEN_labels l)
   { CGEN_labels here;
     CGen->stackaddress(v);
     CGen->stackaddress(v);
     CGen->dereference();
     CGen->negateboolean();
     CGen->assign();
     CGen->stackaddress(v);
     CGen->dereference();
     CGen->negateboolean();
     CGen->jumponfalse(here, l);
   }

   /* ---------------------------------------------------------------------- */

   IGNORE CASE
   IGNORE CHR(9) .. CHR(13)
   COMMENTS FROM "(*" TO "*)" NESTED

   CHARACTERS
     letter     = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" .
     digit      = "0123456789" .

   TOKENS
     identifier = letter { letter | digit | "_" } .

   PRODUCTIONS
     Truth
     = Logic { WEAK ";" Logic }    (. CGen->leaveprogram(); .)
       EOF .

     Logic
     =                             (. int framesize;
                                      char name[100];
                                      CGEN_labels startstring; .)
       [
       ( [ "NUMERIC" ]             (. WriteAsBoolean = FALSE; .)
         | "BOOLEAN"               (. WriteAsBoolean = TRUE; .)
       )
       Ident<name>                 (. framesize = 0; Table->reset(); .)
       Parameters<framesize>       (. CGen->stackstring(name, startstring);
                                      CGen->writestring(startstring);
                                      CGen->newline();
                                      CGen->openstackframe(framesize); .)
       WEAK "="
       Function<framesize>         (. CGen->openstackframe(-framesize);
                                      CGen->newline();  .)
       ] .

     Parameters<int &framesize>
     = "(" OneVar<framesize> { WEAK "," OneVar<framesize> } ")" .

     OneVar<int &framesize>
     =                             (. TABLE_entries entry;
                                      CGEN_labels startstring; .)
       Ident<entry.name>           (. entry.idclass = TABLE_vars;
                                      entry.v.size = 1; entry.v.scalar = true;
                                      entry.v.offset = framesize + 1;
                                      Table->enter(entry);
                                      framesize++;
                                      CGen->stackstring(entry.name, startstring);
                                      CGen->writestring(startstring); .) .

     Designator
     =                             (. TABLE_entries entry;
                                      TABLE_alfa name;
                                      bool found; .)
       Ident<name>                 (. Table->search(name, entry, found);
                                      if (!found) SemError(202);
                                      CGen->stackaddress(entry.v.offset);
                                      CGen->dereference(); .) .

     Function<int VarsUsed>
     =                             (. int i; .)
                                   (. for (i = 1; i<= VarsUsed; i++)
                                        Prolog1(i, LabelList[i]);
                                      for (i = 1; i<= VarsUsed; i++)
                                        Prolog2(i); .)
       Expression                  (. CGen->stackconstant(6); /* default format for PRN or PRB */
                                      if (WriteAsBoolean) CGen->writeboolean();
                                      else CGen->writevalue();
                                      CGen->newline();
                                      for (i = VarsUsed; i >= 1; i--)
                                        Epilogue(i, LabelList[i]); .) .

     Expression
     = Term
       { Or Term                   (. CGen->binarybooleanop(CGEN_orrop); .)
       } .

     Term
     = Factor
       { [ And ] Factor            (. CGen->binarybooleanop(CGEN_andop); .)
       } .

     Factor
     =   Not Factor                (. CGen->negateboolean(); .)
       | Primary
         { "'"                     (. CGen->negateboolean(); .)
         } .

     Primary
     =   Designator
       | True                      (. CGen->stackconstant(1); .)
       | False                     (. CGen->stackconstant(0); .)
       | "(" Expression ")" .

     And   = "AND"   | "&" | "." .
     Or    = "OR"    | "|" | "+" .
     Not   = "NOT"   | "!" | "~" .
     True  = "TRUE"  | "1" .
     False = "FALSE" | "0" .

     Ident<char *name>
     =  identifier                 (. LexName(name, TABLE_alfalength); .) .

   END Truth.

Pascal Solution

   COMPILER Truth $XNC
   (* Truth Table grammar - no extra m/c codes
      This solution uses the Stack machine, table handler and code generator of
      the Clang compiler.  It generates simple repeat loops automagically through
      calls to the internal routines Prolog and Epilog before evaluating the rhs
      of each expression.  Have a look at Prac 16, Task 6 and Quiz 25.  Quix 25
      actually gave you most of the grammar, while Prac 16 shows how to set up
      the loops.  Ah well, next time you'll study the solutions!
      P.D. Terry, Rhodes University, 1999 *)

   USES CGEN, STKMC, TABLE;

   VAR
     WriteAsBoolean : BOOLEAN;
     LabelList : ARRAY [1 .. 100] OF CGEN.Labels;

   PROCEDURE Prolog1 (V : INTEGER; VAR L : CGEN.Labels);
     BEGIN
       CGEN.StackAddress(V);
       CGEN.StackConstant(0);
       CGEN.Assign;
       CGEN.StoreLabel(L);
     END;

   PROCEDURE Prolog2 (V : INTEGER);
     BEGIN
       CGEN.StackAddress(V);
       CGEN.Dereference;
       CGEN.StackConstant(6);     (* default format for PRN or PRB *)
       IF WriteAsBoolean THEN CGEN.WriteBoolean ELSE CGEN.WriteValue;
     END;

   PROCEDURE Epilogue (V : INTEGER; L : CGEN.Labels);
     VAR
       Here : CGEN.Labels;
     BEGIN
       CGEN.StackAddress(V);
       CGEN.StackAddress(V);
       CGEN.Dereference;
       CGEN.NegateBoolean;
       CGEN.Assign;
       CGEN.StackAddress(V);
       CGEN.Dereference;
       CGEN.NegateBoolean;
       CGEN.JumpOnFalse(Here, L);
     END;

   (* ---------------------------------------------------------------------- *)

   IGNORE CASE
   IGNORE CHR(9) .. CHR(13)
   COMMENTS FROM "(*" TO "*)" NESTED

   CHARACTERS
     letter     = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" .
     digit      = "0123456789" .

   TOKENS
     identifier = letter { letter | digit | "_" } .

   PRODUCTIONS
     Truth
     = Logic { WEAK ";" Logic }    (. CGEN.LeaveProgram; .)
       EOF .

     Logic                         (. VAR
                                        FrameSize : INTEGER;
                                        Name : TABLE.ALFA;
                                        StartString : CGEN.Labels; .)
     = [
       ( [ "NUMERIC" ]             (. WriteAsBoolean := FALSE; .)
         | "BOOLEAN"               (. WriteAsBoolean := TRUE; .)
       )
       Ident<Name>                 (. FrameSize := 0; Table.Reset; .)
       Parameters<FrameSize>       (. CGEN.StackString(Name, StartString);
                                      CGEN.WriteString(StartString);
                                      CGEN.NewLine;
                                      CGEN.OpenStackFrame(FrameSize) .)
       WEAK "="
       Function<FrameSize>         (. CGEN.OpenStackFrame(-FrameSize);
                                      CGEN.NewLine;  .)
       ] .

     Parameters<VAR FrameSize : INTEGER>
     = "(" OneVar<FrameSize> { WEAK "," OneVar<FrameSize> } ")" .

     OneVar<VAR FrameSize : INTEGER>
                                   (. VAR
                                        Entry : TABLE.ENTRIES;
                                        StartString : CGEN.Labels; .)
     = Ident<Entry.Name>           (. Entry.IDClass := Vars; Entry.Size := 1;
                                      Entry.Scalar := TRUE;
                                      Entry.Offset := FrameSize + 1;
                                      TABLE.Enter(Entry);
                                      INC(FrameSize);
                                      CGEN.StackString(Entry.Name, StartString);
                                      CGEN.WriteString(StartString) .) .

     Designator                    (. VAR
                                        Name  : TABLE.ALFA;
                                        Entry : TABLE.ENTRIES;
                                        Found : BOOLEAN; .)
     = Ident<Name>                 (. TABLE.Search(Name, Entry, Found);
                                      IF NOT Found THEN SemError(202);
                                      CGEN.StackAddress(Entry.Offset);
                                      CGEN.Dereference; .) .

     Function<VarsUsed : INTEGER>
                                   (. VAR I : INTEGER; .)
     =                             (. FOR I := 1 TO VarsUsed DO
                                        Prolog1(I, LabelList[I]);
                                      FOR I := 1 TO VarsUsed DO
                                        Prolog2(I); .)
       Expression                  (. CGEN.StackConstant(6); (* default format for PRN or PRB *)
                                      IF WriteAsBoolean
                                        THEN CGEN.WriteBoolean
                                        ELSE CGEN.WriteValue;
                                      CGEN.NewLine;
                                      FOR I := VarsUsed DOWNTO 1 DO
                                        Epilogue(I, LabelList[I]); .) .

     Expression
     = Term
       { Or Term                   (. CGEN.BinaryBooleanOp(opOR); .)
       } .

     Term
     = Factor
       { [ And ] Factor            (. CGEN.BinaryBooleanOp(opAND); .)
       } .

     Factor
     =   Not Factor                (. CGEN.NegateBoolean; .)
       | Primary
         { "'"                     (. CGEN.NegateBoolean; .)
         } .

     Primary
     =   Designator
       | True                      (. CGEN.StackConstant(1); .)
       | False                     (. CGEN.StackConstant(0); .)
       | "(" Expression ")" .

     And   = "AND"   | "&" | "." .
     Or    = "OR"    | "|" | "+" .
     Not   = "NOT"   | "!" | "~" .
     True  = "TRUE"  | "1" .
     False = "FALSE" | "0" .

     Ident<VAR Name : TABLE.ALFA>
                                   (. VAR Str : STRING; .)
     =  identifier                 (. LexName(Str); Name := Str; .) .

   END Truth.

A completely different approach was taken by another set of students. At first I was sceptical that this would work, but once it had been explained and debugged I was quite intrigued. Here it is

  COMPILER Bool $XNC
  /* Bool Table grammar
     P.D. Terry, Rhodes University, 1999 */

  /* Corrected version of Josh Vincent's ideas */

  #include "table.h"
  #include "math.h"
  #include "report.h"

  extern TABLE *Table;
  extern REPORT *Report;

  void calculate (int position, bool array[])
  { int count = 0, segment = 1;
    while (segment <= pow(2, 7 - position))
    { int x = 1;
      while (x <= pow(2, position - 1)) { array[count] = 0; count++; x++; }
      while (x <= pow(2, position))     { array[count] = 1; count++; x++; }
      segment++;
    }
  }

  /* ---------------------------------------------------------------------- */

  IGNORE CASE
  IGNORE CHR(9) .. CHR(13)
  COMMENTS FROM "(*" TO "*)" NESTED

  CHARACTERS
    letter     = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" .
    digit      = "0123456789" .

  TOKENS
    identifier = letter { letter | digit | "_" } .

  PRODUCTIONS
    Bool = BoolStatement { WEAK ";" BoolStatement } EOF .

    BoolStatement
    =                             (. bool Result[128], BoolOutput = false;
                                     int params = 0;
                                     Table->reset(); .)
      [
      ( [ "NUMERIC" ]
        | "BOOLEAN"               (. BoolOutput = true; .)
      )
      Function<params> WEAK "="
      Expression<Result>          (. for (int j = 0; j <pow(2, params); j++)
                                     { for (int i = 0; i < params; i++)
                                       { bool pvalue = Table->getval(i+1, j);  // error here
                                         if (BoolOutput) {
                                           if (pvalue) printf("  TRUE"); else printf(" FALSE");
                                         }
                                         else printf("%6d", pvalue);
                                       }
                                       if (BoolOutput) {
                                         if (Result[j]) printf("  TRUE\n"); else printf(" FALSE\n");
                                       }
                                       else printf("%6d\n", Result[j]);
                                     }
                                  .)
      ] .

    Function<int ¶ms>
    =                             (. char fname[16]; .)
      identifier                  (. LexName(fname, sizeof(fname) - 1); .)
      "(" OnePar<params>
          { WEAK "," OnePar<params> } ")"
                                  (. printf("%6s\n", fname); .)
      .

    OnePar<int ¶ms>
    =                             (. TABLE_entries entry; .)
      identifier                  (. LexName(entry.name, TABLE_alfalength);
                                     printf("%6s", entry.name);
                                     params++;
                                     calculate(params, entry.BoolState);
                                     Table->enter(entry);
                                  .) .

    Expression<bool Result[]>
    = AndExp<Result>              (. bool Result2[128]; .)
      { Or AndExp<Result2>        (. for (int i = 0; i < 128; i++)
                                       Result[i] = Result[i] || Result2[i];
                                  .)
      } .

    AndExp<bool Result[]>
    = NotExp<Result>              (. bool Result2[128]; .)
      { [ And ] NotExp<Result2>   (. for (int i = 0; i < 128; i++)
                                       Result[i] = Result[i] && Result2[i];
                                  .)
      } .

    NotExp<bool Result[]>
    =                             (. bool Result2[128]; .)
       PreNot NotExp<Result>      (. for (int i = 0; i < 128; i++)
                                       Result[i] = !Result[i];
                                  .)
      | Factor<Result>
        { "'"                     (. for (int i = 0; i < 128; i++)
                                       Result[i] = !Result[i];
                                  .)
        } .

    Factor<bool Result[]>
    =                             (. char id[16]; int i;
                                     bool found; TABLE_entries entry; .)
        identifier                (. LexName(id, sizeof(id) - 1);
                                     Table->search(id, entry, found);
                                     if (!found) SemError(202);
                                     for (i = 0; i < 128; i++)
                                       Result[i] = entry.BoolState[i];
                                  .)
      | True                      (. for (i = 0; i < 128; i++) Result[i] = true; .)
      | False                     (. for (i = 0; i < 128; i++) Result[i] = false; .)
      | "(" Expression<Result> ")" .

    And      = "AND"   | "&" | "." .
    Or       = "OR"    | "|" | "+" .
    PreNot   = "NOT"   | "!" | "~" .
    True     = "TRUE"  | "1" .
    False    = "FALSE" | "0" .

  END Bool.
</PRE>

With this system the table handler was modified:

<PRE>

  // Handle symbol table for Josh's truth tables

  #ifndef TABLE_H
  #define TABLE_H

  #include "misc.h"
  #include "report.h"

  const int tablemax = 100;        // max number of identifiers

  const int TABLE_alfalength = 15; // maximum length of identifiers
  typedef char TABLE_alfa[TABLE_alfalength + 1];

  struct TABLE_entries {
    TABLE_alfa name;             // identifier
    bool BoolState[128];
  };

  class TABLE {
    public:
      void enter(TABLE_entries &entry);
      // Adds entry to symbol table

      void search(char *name, TABLE_entries &entry, bool &found);
      // Searches table for presence of name.  If found then returns entry

      bool getval(int x, int y);
      // get boolean value of BoolState[x, y]

      TABLE(REPORT *R);
      // Initializes symbol table

      void reset(void);
      // Clear table

    private:
      int top;
      TABLE_entries symtable[tablemax + 1];
      REPORT *Report;
      void find(char *name, int &look);
  };

  #endif /*TABLE_H*/

  // Handle symbol table for Josh's truth tables

  #include "table.h"

  void TABLE::find(char *name, int &look)
  { look = top;
    sprintf(symtable[0].name, "%.*s", TABLE_alfalength, name);
    while (strcmp(symtable[look].name, symtable[0].name)) look--;
  }

  void TABLE::enter(TABLE_entries &entry)
  { int look;
    find(entry.name, look);
    if (look != 0) // check current scope for duplicates
      Report->error(201);
      else
      { if (top == tablemax)  // Symbol Table overflow
          { printf("Symbol table full\n"); exit(1); }
        top++; symtable[top] = entry;
      }
  }

  void TABLE::search(char *name, TABLE_entries &entry, bool &found)
  { int look;
    find(name, look);
    found = (look != 0);
    entry = symtable[look];
  }

  bool TABLE::getval(int x, int y)
  { return symtable[x].BoolState[y]; }

  void TABLE::reset(void)
  { top = 0; symtable[top].name[0] = '\0'; }

  TABLE::TABLE(REPORT *R)
  { Report = R; reset(); }