Computer Science 301 - 2007

Tutorial for week 25 - Constraint analysis - solution

1. A simplified version of the SwitchStatement has syntax that can be described by

         SwitchStatement = "switch" "(" Expression ")" "{"
                             { OneSwitch }
                           "}" .
         OneSwitch       = CaseLabel ":" { CaseLabel ":" } Statement { Statement } .
         CaseLabel       = "case" Constant | "default" .
         Constant        = IntConst | CharConst | "true" | "false" | "null" .

but, as usual, this has to be supplemented by semantic constraints. Among these are, fairly obviously, that no Constant may appear more than once as a CaseLabel within a single SwitchStatement, that the types of all the constants and the type of the selector Expression must be compatible, and that there may be only one default label. Although not illegal, a SwitchStatement without any instances of OneSwitch is of little use, and might elicit a warning to the user if it is detected.

How would the productions be attributed to allow such constraint checking to proceed?

2. In C, C++ and Java the statements that follow each CaseLabel are not restricted in any way, although it is common to find that the last of them is a BreakStatement or ReturnStatement; if absent the semantics prescribe that control then falls through to the next option. This is expressly forbidden in C#, which requires that if the Statement sequence is not empty it must terminate with an unguarded explicit BreakStatement or ReturnStatement.

Develop the productions given above in such a way that this C# constraint is enforced.


After some thought it appears that this might best be handled by introducing a new class to record the properties of a SwitchStatement that need to be passed to and fro between the various sub parsers. This overcomes the inability of Java to provide proper reference parameters:

  class SwitchRec {
  // objects of this type are associated with switch statements
    public ArrayList<Integer> labelList = new ArrayList<Integer>();  // the list of labels
    public int defaultCount = 0;                                     // the number of default options
    public int selType = Entry.noType;                               // the type of the selector expression
  }

We also need reminding of the class used to record properties of a constant:

  class ConstRec {
  // Objects of this type are associated with literal constants
    public int value;            // value of a constant literal
    public int type;             // constant type (determined from the literal)
  }

These properties are set when we parse a Constant:

  Constant<out ConstRec con>            (. con = new ConstRec(); .)
  =   IntConst<out con.value>           (. con.type = Entry.intType; .)
    | CharConst<out con.value>          (. con.type = Entry.intType; .)
    | "true"                            (. con.type = Entry.boolType; con.value = 1; .)
    | "false"                           (. con.type = Entry.boolType; con.value = 0; .)
    | "null"                            (. con.type = Entry.nullType; con.value = 0; .)
  .

The top parser for SwitchStatement creates the (local) object of the SwitchRec class and determines the type of the selector Expression. It also deals with the check that there really are some options to exercise!

  SwitchStatement<out int statType>     (. SwitchRec switchDetails = new SwitchRec(); .)
  = "switch" "("                        (. statType = switchStat;
                                           boolean hasOptions = false; .)
    Expression<out switchDetails.selType>
    ")" "{"
      { OneSwitch<switchDetails>        (. hasOptions = true; .)
      }
    "}"                                 (. if (!hasOptions) Warning("empty switch statement"); .)
    .

The switchDetails object is then passed down to the OneSwitch parser as often as necessary. This parser hands it on to the CaseLabel parser, but is also responsible for checking that the last statement in each sequence is a break or return statement. This is actually not an ideal test, as it would not be satisfied by code like

       case 12 : if (a > b) break; else break;

if one wanted to be particularly perverse (it's always possible to be perverse!). To get all this to work it will be necesssary to attribute the Statement parser to be able to return a value signifying the kind of statement that has been parsed. Full details will not be given here, but the reader should be able to get the sense of this from the fragment below. In practice in Java and C# compilers rather more sophisticated ways are found of doing these sorts of checks.

  OneSwitch<SwitchRec switchDetails>    (. int statementKind = emptyStat; .)
  = CaseLabel<switchDetails> ":"
    { CaseLabel<switchDetails> ":" }
    Statement<out StatementKind>
    { Statement<out statementKind> }    (. if (statementKind != emptyStat &&
                                               statementKind != breakStat &&
                                               statementKind != returnStat)
                                             SemError("break or return needed here"); .)
  .

The CaseLabel parser determines the (essentially integral) value of each label introduced by a case keyword, then checks that its type is correct and that the label has not been used before. In the case where the option is specified as default the check for an unwanted duplicate must be carried out in some other way. The suggested test below is arranged so that only the second of a possible sequence of default options will be highlighted:

  CaseLabel<SwitchRec switchDetails>    (. ConstRec con; .)
  =   "case" Constant<out con>          (. if (con.type != switchDetails.selType)
                                             SemError("label type must match selector type");
                                           if (labelFound(switchDetails.labelList, con.value))
                                             SemError("switch labels must be unique"); .)
    | "default"                         (. if (switchDetails.defaultCount == 1)
                                             SemError("only one default option allowed");
                                           switchDetails.defaultCount++; .)
  .

Finally, the maintenance of the label list might be carried out by a method similar to several seen in recent practicals:

  static boolean labelFound(ArrayList<Integer> list, int value) {
  // Returns true if value is contained in list, otherwise add value to list
    int i = 0, length = list.size();
    while (i < length && value != list.get(i)) i++;
    if (i == length) list.add(value);                 // was not there
    return i < length;
  }


Home  © P.D. Terry