Computer Science 3 - 2002

Programming Language Translation


Practical for Week 12, beginning 6 May 2002 - Solutions

On the whole this prac was well done. As most people discovered, the additions needed to the PARVA.ATG file were usually small - the problem was to locate them all:

The solution kit PRAC12A.ZIP has the full sources for a complete solution, including the GoToStatement as discussed in the prac test. The extracts below show the changes from the original marked in an obvious way.

We needed an extension to the Statement production to handle the extra statement forms:

      Statement<int &framesize, CGEN_LabelList &labellist, CGEN_LabelList &gotolist>
****  =  SYNC (   Label<labellist>
****            | GoToStatement<gotolist>
                | Block<framesize>
                | Assignment
****            | IfStatement<framesize, gotolist>
                | WhileStatement<framesize>
                | ReadStatement
                | WriteStatement
****            | ReturnStatement
****            | DoStatement<framesize>
                | ";"
                | ConstDeclaration
                | VarDeclarations<framesize>
****            | "stackdump"       (. if (Report->debugging) CGen->dump() .)
              ) .


Task 4 - Better use of the debugging pragma

This was pretty trivial. Most people at least got the definition of the pragma correct:

      PRAGMAS
        DebugOn    = "$D+" .          (. Report->debugging = true; .)
****    DebugOff   = "$D-" .          (. Report->debugging = false; .)

but several missed out on seeing how to use it to control output of the .COD file, which needed a change to PARVA.FRM. Note that we need the call to CGen->getsize regardless. The comment is really in the wrong place - it should come after the CGen->getsize call, not before it. But of course nobody reads comments anyway, so why bother (until you get even more confused!)

      // list generated code for interest
      CGen->getsize(codelength, initsp);
      appendextension(SourceName, ".cod", CodeName);
****  if (Report->debugging) Machine->listcode(CodeName, codelength);


Task 5 - Learning multiple languages can be frustrating

Trapping the illegal uses of operators caused few problems:

      AssignOp
      =    "="
****     | ":="                     (. SemError(210); .) .

      EqualOp<CGEN_operators &op>
      =                             (. op = CGEN_opeql; .)
       (   "=="
****     | "="                      (. SemError(91); .)
****     | "<>"                     (. SemError(91); .)
         | "!="                     (. op = CGEN_opneq; .)
       ) .


Task 6 - Friendlier array declarations

This was not very well done. Many people just assumed that everything would be correct, and did not do enough checking. Here is a suggested solution, which also does a forced entry to the symbol table if the identifier was not declared. This is a useful trick to try to prevent a whole lot of subsequent errors if one has just mistyped an identifier name - an easy thing to do.

      ArraySize<int &size>
      =                             (. TABLE_entries entry;
                                       bool found; .)
         "[" (   Number<size>
****           | Ident<entry.name>  (. Table->search(entry.name, entry, found);
****                                   if (!found) {
****                                     SemError(202); // force entry
****                                     entry.idclass = TABLE_consts;
****                                     entry.type = TABLE_ints;
****                                     entry.c.value = 1;
****                                     Table->enter(entry);
****                                   }
****                                   if (entry.idclass != TABLE_consts) {
****                                     SemError(206); size = 1;
****                                   } else size = entry.c.value;
****                                   if (entry.type != TABLE_ints) SemError(218); .)
****                                .)
****          )                     (. if (size == 0) SemError(228); .)
         "]" .


Task 7 - More on scope rules

This was badly done, yet the solution was trivially easy - a small change to the enter function of the table handler from

      if (look > scopetable[scope].top) // check current scope for duplicates

to the code shown below was all that was needed. Note that the enter routine is by far the best place to look for duplication/scope rule violations, as these have to be applied to all identifiers, not just to variables, as some people tried to do.

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


Task 8 - The return statement

I think everyone got this one!

****  ReturnStatement
****  =  "return" WEAK ";"          (. CGen->leaveprogram(); .) .


Task 9 - Prompts in read statements

There were some clumsy solutions, many of which "worked" anyway - but it is a good idea to seek for elegance, which does not come from the ability to "cut and paste" too freely:

      ReadStatement
****  =  "read" "(" ReadElement { WEAK "," ReadElement } ")" WEAK ";" .

      ReadElement
      =                             (. TABLE_types vartype;
****                                   char str[600];
****                                   CGEN_labels startstring; .)
****      String<str>               (. CGen->stackstring(str, startstring);
****                                   CGen->writestring(startstring); .)
        |
          Variable<vartype>         (. switch (vartype)
                                       { case TABLE_ints  : CGen->readintvalue(); break;
                                         case TABLE_bools : CGen->readboolvalue(); break;
****                                     case TABLE_chars : CGen->readcharvalue(); break;
                                         case TABLE_none  : /* error already */   break;
                                       } .) .


Task 10 - Something to do - while you wait for a tutor

There are several possibilities for doing this. Here is one that makes use of the existing opcode set only:

****  DoStatement<int &framesize>
****  =                             (. CGEN_labels startloop, dummylabel ; .)
****     "do"                       (. CGen->storelabel(startloop) .)
****       Block<framesize>
****     WEAK "while"
****     WEAK "(" Condition
****     WEAK ")" WEAK ";"          (. CGen->negateboolean();
****                                   CGen->jumponfalse(dummylabel, startloop);
****                                .) .

Here is another that generates some extra branches to get the right effect:

****  DoStatement<int &framesize>
****  =                             (. CGEN_labels startloop, testlabel, dummylabel ; .)
****     "do"                       (. CGen->storelabel(startloop) .)
****       Block<framesize>
****     WEAK "while"
****     WEAK "(" Condition
****     WEAK ")" WEAK ";"          (. CGen->jump(testlabel, CGen->undefined);
****                                   CGen->jumponfalse(dummylabel, startloop);
****                                   CGEN->backpatch(testlabel);
****                                .) .

And of course one could have added to the opcode set:

****  DoStatement<int &framesize>
****  =                             (. CGEN_labels startloop, testlabel, dummylabel ; .)
****     "do"                       (. CGen->storelabel(startloop) .)
****       Block<framesize>
****     WEAK "while"
****     WEAK "(" Condition
****     WEAK ")" WEAK ";"          (. CGen->jumpontrue(dummylabel, startloop); .) .


Task 11 - You had better do this one or else....

This was discussed further in the prac test. The code I hoped people might come up with (but nobody did, so far as I recall) aims to eliminate unnecessary branch instructions in the absence of the else part

      IfStatement<int &framesize>
      =                             (. CGEN_labels testlabel, outlabel; .)
         "if" "(" Condition ")"     (. CGen->jumponfalse(testlabel, CGen->undefined); .)
         Block<framesize>
****     (   "else"                 (. CGen->jump(outlabel, CGen->undefined);
****                                   CGen->backpatch(testlabel); .)
****         Block<framesize>       (. CGen->backpatch(outlabel); .)
****       | /* no else part */     (. CGen->backpatch(testlabel); .)
****     ) .

Many people produced code that would only have worked if the else part had been present.


Task 12 - The character data type

This was easy enough, but needed various small additions. To declare constants:

      ConstDeclaration
      =                             (. int value;
                                       TABLE_entries entry; .)
         "const" Ident<entry.name>  (. entry.idclass = TABLE_consts; .)
         AssignOp
         (   Number<value>          (. entry.c.value = value; entry.type = TABLE_ints; .)
****       | Char<value>            (. entry.c.value = value; entry.type = TABLE_chars; .)
           | "true"                 (. entry.c.value = 1; entry.type = TABLE_bools; .)
           | "false"                (. entry.c.value = 0; entry.type = TABLE_bools; .)
         ) WEAK ";"                 (. Table->enter(entry); .) .

To declare variables

      VarDeclarations<int &framesize>
      =                             (. TABLE_types type; .)
         (   "int"                  (. type = TABLE_ints .)
****       | "char"                 (. type = TABLE_chars .)
           | "bool"                 (. type = TABLE_bools .)
         )
         OneVar<framesize, type> { WEAK "," OneVar<framesize, type> } WEAK ";" .

To allow for input and output:

      ReadElement
      =                             (. TABLE_types vartype;
****                                   char str[600];
****                                   CGEN_labels startstring; .)
****      String<str>               (. CGen->stackstring(str, startstring);
****                                   CGen->writestring(startstring); .)
        |
          Variable<vartype>         (. switch (vartype)
                                       { case TABLE_ints  : CGen->readintvalue(); break;
                                         case TABLE_bools : CGen->readboolvalue(); break;
****                                     case TABLE_chars : CGen->readcharvalue(); break;
                                         case TABLE_none  : /* error already */   break;
                                       } .) .
      WriteElement
      =                             (. TABLE_types exptype;
                                       char str[600];
                                       CGEN_labels startstring; .)
          String<str>               (. CGen->stackstring(str, startstring);
                                       CGen->writestring(startstring); .)
        | Expression<exptype>       (. switch (exptype)
                                       { case TABLE_ints  : CGen->writeintvalue(); break;
                                         case TABLE_bools : CGen->writeboolvalue(); break;
****                                     case TABLE_chars : CGen->writecharvalue(); break;
                                         case TABLE_none  : /* error already */    break;
                                       } .) .

To allow for factors in expressions to be character literals:

      Factor<TABLE_types &f>
      =                             (. int value;
                                       TABLE_entries entry; .)
           Designator<classset(TABLE_consts, TABLE_vars), entry, f>
                                    (. switch (entry.idclass)
                                       { case TABLE_vars :
                                           CGen->dereference(); break;
                                         case TABLE_consts :
                                           CGen->stackconstant(entry.c.value); break;
                                       } .)
         | Number<value>            (. CGen->stackconstant(value); f = TABLE_ints; .)
****     | Char<value>              (. CGen->stackconstant(value); f = TABLE_chars; .)
         | "true"                   (. CGen->stackconstant(1); f = TABLE_bools .)
         | "false"                  (. CGen->stackconstant(0); f = TABLE_bools .)
         | "(" Expression<f> ")" .

To allow for parsing of character literals I suggest code like that below. There were some horrible hacks submitted - but note that one has to be able to recognize literals like 'A' and '\a' and '\\' and '\z'

      Char<int &num>
      =                             (. char str[5]; .)
        character                   (. LexString(str, sizeof(str) - 1);
                                       if (str[1] != '\\') num = str[1];
                                       else switch(str[2])
                                       { case 'a' : num = 7; break;
                                         case 'b' : num = 8; break;
                                         case 't' : num = 9; break;
                                         case 'n' : num = 10; break;
                                         case 'f' : num = 12; break;
                                         case 'r' : num = 13; break;
                                         default  : num = str[2]; break;
                                       } .) .

Finally we need to deal with compatibility issues. In the model solution we have regarded the char type (as in C++ and Java) essentially to be compatible with the int type. This is achieved by the simple change below:

      Parva
      =                             (. int framesize;
                                       CGEN_labels enterlabel; .)
         "void" "main" "(" [ "void" ] ")"
****                                (. arithtypes = typeset(TABLE_none, TABLE_ints, TABLE_chars);
                                       booltypes  = typeset(TABLE_none, TABLE_bools);
                                       framesize = 0; maxframe = 0;
                                       CGen->openstackframe(enterlabel); .)
         Block<framesize>           (. /* reserve space for variables */
                                       CGen->fixup(enterlabel, maxframe);
                                       CGen->leaveprogram(); .) .

Alternatively we might proceed as follows:

****  typeset arithtypes, booltypes, chartypes;

      bool compatible (TABLE_types atype, TABLE_types btype)
      // returns true if atype is compatible with btype
      { return arithtypes.memb(atype) && arithtypes.memb(btype)
               || booltypes.memb(atype) && booltypes.memb(btype);
****           || chartypes.memb(atype) && chartypes.memb(btype);
      }

      Parva
      =                             (. int framesize;
                                       CGEN_labels enterlabel; .)
         "void" "main" "(" [ "void" ] ")"
                                    (. arithtypes = typeset(TABLE_none, TABLE_ints, TABLE_chars);
                                       booltypes  = typeset(TABLE_none, TABLE_bools);
****                                   chartypes  = typeset(TABLE_none, TABLE_chars);
                                       framesize = 0; maxframe = 0;
                                       CGen->openstackframe(enterlabel); .)
         Block<framesize>           (. /* reserve space for variables */
                                       CGen->fixup(enterlabel, maxframe);
                                       CGen->leaveprogram(); .) .

Incidentally, people obviously missed the full significance of the "none" type. Not only is it useful for undeclared variables, it is useful in expression analysis for suppressing a whole host of "type mismatch" errors, as the none type is deemed to be compatible with all other types!


Task 13 - Make the change; enjoy life; upgrade now to Parva++ (Ta-ra!)

This is fairly simple. Most people saw the syntactic change needed, fewer saw that the ++ and -- operators can only be applied to some types of variables, and many obviously missed the point of the range checking code:

      Assignment
      =                             (. TABLE_types vartype, exptype;
                                       TABLE_entries entry; .)
         Designator<classset(TABLE_vars), entry, vartype>
         (   AssignOp
             Expression<exptype>    (. if (compatible(vartype, exptype))
                                         CGen->assign(vartype == TABLE_chars);
                                       else SemError(218); .)
****       | "++"                   (. if (arithtypes.memb(vartype))
****                                     CGen->increment(vartype == TABLE_chars);
****                                     else SemError(218); .)
****       | "--"                   (. if (arithtypes.memb(vartype))
****                                     CGen->decrement(vartype == TABLE_chars);
****                                     else SemError(218); .)
         ) WEAK ";" .


Home  © P.D. Terry