As seen last time, the primary aspects of the translation of control structures are
With respect to this final point consider the following examples.
The Case statement:
The important thing to remember is that although we can study some of the basic aspects of translating For and Case statements, many details will be different based on the specifications of the programming language.
Case statements require more thought to translate well. The first approach that comes to mind is to translate the Case statement the same way an If with Elsif clauses is translated. That would mean that every case clause (e.g. each When) is evaluated, and if the case value doesn't match one of the current when values, a jump to the next When clause is inserted. It would be better if it were possible to jump straight to the right case without being required to check each case clause individually. This can be accomplished with a jump table.
Consider the following case statement:
Case Choice is
when 3 =>
Statements 1
when 6 ==>
Statements 2
when 4,7 =>
Statements 3
when others =>
Statements 4
end Case
Suppose that Choice is of type Integer with range 1..10 (that is, the only values Choice can have in it are 1 through 10). The translated case body might look like:
Code to evaluate case expression (Choice) Code to jump to the proper when clauseLabel L1 Code for Statements 1 Branch OutLabel L2 Code for Statements 2 Branch OutLabel L3 Code for Statements 3 Branch OutLabel L4 Code for Statements 4 Branch OutLabel Out
This looks fairly straightforward except for one thing. How can we
generate code that will jump to the proper label based on the value in the case
expression? For instance, in our example, if Choice evaluates to 7, we
would need to have code that would jump to label L3. The trick is to build
into the code something we call a jump table. The jump table could have
entries for every possible case, as in the following
Jump L4 ;case 1 Jump L4 ;case 2 Jump L1 ;case 3 Jump L3 ;case 4 Jump L4 ;case 5 Jump L2 ;case 6 Jump L3 ;case 7 Jump L4 ;case 8 Jump L4 ;case 9 Jump L4 ;case 10
Notice that for all 10 possible values for Choice (1 through 10) there is an unconditional Jump command that jumps to the proper label for handling that particular case. So, for example, the 4th entry in this jump table corresponds to the case that Choice = 4, and that entry says to Jump to label L3. That's the place in the code where the case that Choice = 4 is handled. Where do we put this jump table? Well, during parsing, we won't know the names of the labels or the values that trigger each When clause until we are done parsing the entire Case statement, so it looks like it must go at the end of the case statement translation. This gives
Code to evaluate case expression (Choice) Jump L5 + Choice ;Code to jump to the proper when clauseLabel L1 Code for Statements 1 Branch OutLabel L2 Code for Statements 2 Branch OutLabel L3 Code for Statements 3 Branch OutLabel L4 Code for Statements 4 Branch OutLabel L5 Jump L4 ;case 1 Jump L4 ;case 2 Jump L1 ;case 3 Jump L3 ;case 4 Jump L4 ;case 5 Jump L2 ;case 6 Jump L3 ;case 7 Jump L4 ;case 8 Jump L4 ;case 9 Jump L4 ;case 10Label Out
Notice the green changes. We put in a jump to label L5 + Choice, and we make L5 the label of the start of the jump table. After evaluating Choice, the result can be added to the label address for L5 to get to the proper position in the jump table. Once there, that entry causes a second jump to the proper label for handling that case. Notice that Label Out is after the jump table for obvious reasons.
It looks like the semantic analyzer must do a few different things to build the jump table properly. First, each time a When clause is encountered, a label must be generated, and then this label must be kept in a list of labels associated with the values triggering that When clause. When the end case statement is encountered, the semantic analyzer must use this list to generate the table as shown.
The table, of course, could get quite large, especially if Choice could take on any integer value in our example. In this case, the table is just trimmed down to hold only those values between the maximum and the minimum values that actually show up in one of the When clauses. If choice evaluates to a value outside of this range, the others clause is selected automatically. This would give:
Code to evaluate case expression (Choice) Code to Compare Choice with MinWhenValue Code to Jump L4 if Less Code to Compare Choice with MaxWhenValue Jump L4 if Greater ;Code to jump to the proper when clause Jump L5 + size_of_jump_instruction * (Choice - MinValue)Label L1 Code for Statements 1 Branch OutLabel L2 Code for Statements 2 Branch OutLabel L3 Code for Statements 3 Branch OutLabel L4 Code for Statements 4 Branch OutLabel L5 Jump L1 ;case 3 Jump L3 ;case 4 Jump L4 ;case 5 Jump L2 ;case 6 Jump L3 ;case 7Label OutSome issues
- We might not know MinWhenValue and MaxWhenValue until the entire Case statement has been parsed. One way to handle this is shown below. Another way is to perform what is called "backpatching." Backpatching refers to "going back in the translated code and patching up or patching in some statements that could not be completed until more information about the program is uncovered as the compilation progresses."
- We have inserted enough extra checks that for small case statements, it might be better to translate them as we do if-then-elsif-else statements.
- In some cases the table could still be very large. In that case we might want to follow the if model of translation.
Code to evaluate case expression (Choice) Jump L0Label L1 Code for Statements 1 Branch OutLabel L2 Code for Statements 2 Branch OutLabel L3 Code for Statements 3 Branch OutLabel L4 Code for Statements 4 Branch OutLabel L5 Jump L1 ;case 3 Jump L3 ;case 4 Jump L4 ;case 5 Jump L2 ;case 6 Jump L3 ;case 7Label L0 Code to Compare Choice with MinWhenValue Code to Jump L4 if Less Code to Compare Choice with MaxWhenValue Jump L4 if Greater ;Code to jump to the proper when clause Jump L5 + size_of_jump_instruction * (Choice - MinValue)Label Out
Notice the parts in red. We have inserted code to check the evaluation of Choice with the minimum and maximum values that appears in any of the when clauses. If the value is outside this range, we just jump to the others clause directly. Otherwise, we jump to the Jump table, which has now been trimmed to have rows only for each value between the minimum and maximum represented in the When clauses.
The For statement:
Suppose we were translating the loop:
for I in Start..Start*10 loop statements end loop;
Suppose that this programming language spedifies that the loop termination condition (Start*10) be evaluated only once. Then we might have code similar to
code to compute starting expression (Start) code to compute ending expression (Start*10), save in end_value code to compare Start > Start*10 Branch on true to Out code to set index to starting value expression (I <-- Start) Label Top_Of_Loopcode for statementsCheck Index for equality with end_value Branch on True to Out Code to increment the index Branch Top_Of_Loop Label Out
Besides generating this code, the compiler would need to generate a new symbol table for this loop with the loop index (I) and its type as the only entry, if this language specifies that the loop index variable is declared at the beginning of the loop and only exists during the loop. At the end of the loop, this symbol table would have to be destroyed. The semantic analyzer would also have to check that the type of I is proper for a loop.
You can see what changes would need to be made for different language specifications. For example, if the ending value for the loop had to be recomputed each time through the loop, the code for recomputing the expression would need to be included inside the loop rather than outside the loop.
Consider a typical class:
public class CounterClass
{
private int counter = 0;
public static final char BOY = 'M'; public static final char GIRL = 'F';
public CounterClass2()
{
// This constructor does nothing. Use it when you want to start
// the counter at 0.
}
public CounterClass2(int startingValue)
{
counter = startingValue;
}
public void plusOne()
{
counter++;
}
public void minusOne()
{
counter--;
}
public int getCounter()
{
return counter;
}
}
Now consider a use of this class in declaring objects.
public class Countem
{
public static void main (String[] args) throws Exception
{
CounterClass numberOfBoys = new CounterClass();
CounterClass numberOfGirls = new CounterClass(10);
char gender;
while (true)
{
System.out.print("Please enter M for a boy or F for a girl > ");
gender = BasicIo.readCharacter();
if (gender == 'M')
{
numberOfBoys.plusOne();
}
else
{
numberOfGirls.plusOne();
}
System.out.println("Boys = " + numberOfBoys.getCounter());
System.out.println("Girls = " + numberOfGirls.getCounter());
System.out.println("Total = " + numberOfBoys.getCounter() + numberOfGirls.getCounter());
}
}
}
How do you suppose classes and objects might be compiled?