Removing the Requirement for Braces in Jack's "if" and "while" Statements

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Removing the Requirement for Braces in Jack's "if" and "while" Statements

cadet1620
Administrator
This post was updated on .
It is rather annoying that single statements in the bodies of if and while statements need to be surrounded by {}. This is particularly ugly for "else if" structures.
if (a) {
    do option1();
}
else { 
    if (b) {
        do option2();
    }
    else {
        if (c) {
            do option3();
        }
        else {
            do default();
        }
    }
}
It would be quite nice if this could be written without all the {}.
if (a)
    do option1();
else if (b)
    do option2();
else if (c)
    do option3();
else
    do default();
It is a minor addition to Jack syntax to allow this, and an easy change to the CompilationEngine module to support the new syntax.

Syntax changes

The book's syntax for if and while statements is

ifStatement:  'if ' '(' expression ')' '{' statements '}' ( 'else' '{' statements '}' )?
whileStatement: 'while' '(' expression ')' '{' statements '}'

To support if and while statements without braces, the
  '{' statements '}'
in the if/while syntax needs to also allow a single statement. It changes to
  ( statement | '{' statements '}' )

The syntax will be made a bit cleaner by adding the new construct "block".

ifStatement:  'if ' '(' expression ')' (statement | block) ( 'else' (statement | block) )?
whileStatement: 'while' '(' expression ')' (statement | block)
block: '{' statements '}'

Code changes

Changes to compileWhile() are straightforward.

After compiling the controlling expression and the ')', where you currently check for '{' and then call compileStatements(), you need to insert a check for a keyword token which indicates the beginning of a single statement.

    ...
    compileExpression()
    expect ')'
    write ASM code for loop break
+   if tokenType is KEYWORD
+       compileStatement()
+   else
        expect '{'
        compileStatements()
        expect '}'
+   endif
    ...
Note: expect in this context means consume the next token and throw an error if it was not the correct token.

If you followed the book design, you may need to move single statement compilation from compileStatements() into compileStatement().

Changes to compileIf() are similar to those for compileWhile().

Test Code

/** Test code for "no braces" compiler modification.
 *
 *  Should print "one...two...three...many...many...".
 */

class Main {
    function void main() {
        var int i;
        let i = 1;
        while (i < 6)
            let i = Main.test(i);
        return;
    }

    function int test(int x) {
        if (x > 3)
            do Output.printString("many...");
        else if (x > 2)
            do Output.printString("three...");
        else if (x > 1)
            do Output.printString("two...");
        else
            do Output.printString("one...");
        return x+1;
    }
}
Reply | Threaded
Open this post in threaded view
|

Re: Removing the Requirement for Braces in Jack's "if" and "while" Statements

Erik Umble
So that no one has to go through the hours of debugging that I just did... if you implement this, make sure your tokenizer is not assuming there will be a symbol such as '{' or '}' or ';' at the end of each line. I searched every line of code in my compileIfStatement many times before checking the tokens and realizing that my tokenizer had thrown out the 'else' that was on its own line, since before now, any identifiers or keywords had some symbol that would follow them before the next line.
Reply | Threaded
Open this post in threaded view
|

Re: Removing the Requirement for Braces in Jack's "if" and "while" Statements

WBahn
Administrator
The reason that the current syntax is the way that it is is to avoid having to deal with the dangling-else problem. The grammar that cadet1620 proposed is ambiguous and his test didn't test for the ambiguous case.

Consider the following:

c = 0
if (a = 10)
   if (b = 20)
      c = 100
   else
      c = 200

If a = 10 and b = 30, what should c be at the end?
if a = 50 and b = 30, what should c be at the end?

Now how about this:

c = 0
if (a = 10)
   if (b = 20)
      c = 100
else
   c = 200

Same questions.

If your compiler doesn't produce the same answer for both cases, then your compiler is ambiguous because these are the exact same code snippets (since Jack ignores indentation).

Most languages adopt the convention that an else-clause is matched to the structurally nearest unmatched if-statement. The grammar to enforce this convention is more complicated and harder for students at the level of the target audience to grasp.