Computer Science 3 - 2002

Programming Language Translation


Practical for Week 13, beginning 6 May 2002

This prac is due for submission by lunch time on Monday 20 May, correctly packaged in a transparent folder as usual. Pracs should please be deposited in the hand-in box outside the lab. Only one set of listings is needed for each group, but please enclose as many copies of the cover sheet as are needed, one for each member of the group. These will be returned to you in due course.


Objectives

You wanted a short prac: here it is! The objectives of this practical are to allow you to extend the compiler for Parva to allow for the definition of simple void functions which can communicate using globally declared variables, as well as having their own local variables. While that is the main objective, those of you who like further challenges will find two bonus questions as well.


To hand in:

This week you are required to hand in, besides the cover sheet:

I do NOT require listings of any C++ code produced by Coco/R.


Task 1 Buy the kit (for free!)

In the kit PRAC13.ZIP you will find a model solution for last week's practical, which already has some extensions that will make this week's exercise simpler. A listing of relevant part of the attribute grammar and support files appears on a separate handout.


Task 2 - Study the system first (that will cost you!)

In getting to understand the system it is recommended that you take some time to study it before leaping in to modify it.

Have a look at how the solutions to last week's exercises have been incorporated, and then go on to look at the way we might handle global and local identifiers.

If you study the code that handles the declaration of constants and variables you will see that this now makes provision for specifying the level at which this has been done. Level 1 applies to identifiers declared "within" a function - as in

      void routine () {
        const c1 = 2;
        int local1, local2, localList[c1];
      }

while level 0 applies to identifiers declared in a global context, but which are "in scope" in all functions, as exemplified by

      int global1, global2;

      void routine () {
        const c1 = 2;
        int local1, local2, localList[c1];
        local1 = global1 + local2;
      }

Since we cannot "nest" functions, the stack machine and code generator have already been specially enhanced to support a model of run-time storage allocation for this situation as discussed in the tutorial and exemplified by

              Stack frame for routine                           Globals
      .-----------------------------------.        .--------------------------.
      |        |        |        |        |  ....  |        |        |        |
      `-----------------------------------'        `--------------------------'
                                          ^                                   ^
                                          |                                   |
                                          |                                   |
                                       CPU.BP                              CPU.GP

where local variable addresses are pushed onto the run-time stack using the familiar ADR -N opcode by subtracting N from CPU.BP, but where global variable addresses are pushed onto the stack with an analogous (new) opcode GAD -N. It is intended that code for the assignment above would be of the form

            ADR -local1
            GAD -global2
            VAL
            ADR -local2
            VAL
            ADD
            STO

However, the grammar as supplied will only "work" - that is, generate working stack machine code - if you do not declare any global variables, and if you only declare a single "main" function, as for the system used last week. "Make" the system in the ususal way and try it out!

In the distribution kit you will also find a precompiled executable version of the Parva compiler that incorporates the modifications in this practical, as the file PATPARVA.EXE. You are free to try this out, and to compare the code that it generates with the code you generate for yourself.

You will need to approach the rest of the practical very carefully. Here is a suggested order.


Task 3 - Declaring global constants and variables

Modify the system so that it can declare global constants and variables. That should be pretty easy, so try it out on some silly programs that you have no intention of running, and check the symbol table - programs like

   int a, list[10];
   const max = 10;

   void main () {
     int array[max];
     bool clever, correct;
   }


Task 4 - Defining other functions

Modify the system so that you can declare other functions. Try it out on a test program like

    $D+ // debugging on

    void a () {
      int alocal;
    }

    void main () {
      int mainLocal;
    }

without trying to execute the code yet.


Task 5 - Calling functions

Modify the system so that you can call functions and return from them, as exemplified by

    $D+ // debugging on

    void a () {
      int alocal;
      print("in a");
    }

    void main () {
      int mainLocal;
      print("in main");
      a();
    }

You will need to use the code generating functions specified on lines 203 .. 207 of the handout.


Task 6 - Getting it all started

Execution of the program can be achieved by calling the main function. How and where must you generate the code to do this, and what do you do once main has finished execution?


Task 7 - Execution itself

You will need to implement the CAL and RET opcodes in the emulator - at present (lines 263 - 270 of the handout) they are just dummy operations that cause a crash. Bear in mind that calling a routine must set up various linkages in the activation record as discussed in class, and returning from a function must restore various linkages to their prior state. It is easy to get this catastrophically wrong, so be careful!


Task 8 - Accessing global variables

The program above does not use any global variables. One like this would do so - can your system handle it?

    $D+ // debugging on

    int global = 55;

    void a () {
      int alocal;
      print("in a", global);
    }

    void main () {
      int mainLocal;
      print("in main", global);
      global++;
      a();
    }

Compile it, and check the .COD file manually first!


Task 9 - Things that you probably had not thought of!

What happens if the main function is never defined? What happens if you try to declare a function again? What about the scope rules - do you handle something like this correctly?

    int a, b;

    void silly () {
      int a; // local a is legal
      if (a > 1) {
        int b;  // local b is legal
        int a;  // another a is illegal
      }
    }


Task 10 - Function prototypes

Now things start to get really interesting (and subtle). Get the rest of the system working properly in the traditional "declare before use" model first!

To deal with the problem of mutually recursive functions one could introduce the concept of the "function prototype", as exemplified by

      void routine2 ();   // declarative prototype, header only

      void routine1 () {
        routine2();       // call "ahead" to as yet incompletely defined function
      }

      void routine2 () {  // finish defining routine2, predeclared earlier
        routine1();       // call "backward" to already completely defined finction
      }

      void main () {
        routine1();
      }

Firstly, how do you modify the grammar to handle this? Secondly, how do you set up incomplete calls and later complete them? Lastly, how do you check that predeclared functions are properly defined, but that they are not redefined?

Hint: The symbol table handling routines now include a means of updating an existing entry in the table (see lines 190 - 191 of the handout).


Bonus questions:

If you finish all of that in good time you might like to try the following unrelated exercises.


Break statement

One of the simplest statements we could introduce into the language - the break out of a while or do loop - still has to be implemented. I dare you!

Hint: Read the discussion in the textbook in Exercise 15.8.


Optimized variable access

Our stack machine and the code generator allow you to generate and use PSH and POP instructions as discussed in an earlier practical - potentially optimizing the translation of statements like

     pat = 2 * genius;

     ADR  -pat                  LIT  2
     LIT  2                     PSH  -genius
     ADR  -genius               MUL
     VAL                        POP  -pat
     MUL
     STO

How do we achieve this sort of optimization - how do you change the sequence of code generation calls to use these instructions where possible?


Home  © P.D. Terry