Making "let" optional in the Jack Compiler

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

Making "let" optional in the Jack Compiler

cadet1620
Administrator
This post was updated on .
[Be sure to read tungsten's comment after this post — it is a fairly easy addition to this mod to make "do" optional as well.]

This project is to make the let keyword optional in the Jack Compiler. It is easy to make your jack compiler behave similarly to modern languages that use assignments written without a specific keyword.

i = 42;
while (i > 0) {
    do something();
    i = i-1;
}

Syntax changes

The book's syntax for let statements is:

statement:  letStatement | ifStatement | whileStatement | doStatement | returnStatement
letStatement: 'let' varName ( '[' expression ']' )? '=' expression ';'

The required change is to add an assignment construct that replaces the assignment portion of letStatement, and also add assignment as an option in statement.

statement:  assignment ';' | letStatement | ifStatement | whileStatement | doStatement | returnStatement
letStatement: 'let' assignment ';'
assignment: varName ( '[' expression ']' )? '=' expression

Note: LetStatement could have been changed by simply making the "let" optional — ( 'let' )? — but adding assignment better matches how a recursive descent compiler will need to parse the new syntax.

Code changes

All changes are limited to CompilationEngine module.

Most of compileLet() moves into compileAssignment(). All that's left in compileLet() is dealing with the "let" keyword and calling compileAssignment().

Your compileStatements() is probably structured something like this:

while tokenizer.tokenType() is KEYWORD {
    switch tokenizer.keyWord() {
        case LET:
            compileLet()
        case DO:
            compileDo()
        ...
        default:
            throw SyntaxException("Statement expected")
    }
}
It needs to detect when the current token is IDENTIFIER and call compileAssignment() instead of switching on the keyword value.
while tokenizer.tokenType() is KEYWORD or IDENTIFIER {
    if tokenizer.tokenType() is IDENTIFIER
        compileAssignment()
    else {
        switch tokenizer.keyWord() {
        ...

compileStatements() is called from multiple places. If your compiler checks that the current token is KEYWORD before calling compileStatements(), you will also need to check for IDENTIFIER in those places.

Test code

/**
 *  Test code for Jack with optional "let"
 */
class Main {
    function void main() {
        var int i, j, k;    // function with vars starts with assignment
        i = 1;
        let j = 1;
        j = j+1;            // tests that assignments and commands can be mixed

        if (true) {
            i = i+20;       // if block starts with assignment
            let j = j+20;
            j = j+10;
        } else {
            let i = -9999;  // else block starts with command
            j = -8888;
        }

        if (false) {
            let i = -9999;  // if block starts with command
            j = -8888;
        } else {
            i = i+300;      // else block starts with assignment
            let j = j+300;
            j = j+100;
        }

        while (k = 0) {
            i = i+4000;     // while block starts with assignment
            let j = j+4000;
            j = j+1000;
            let k = 1;
        }

        while(false) {
            let i = -9999;  // while block starts with command
            j = -8888;
        }

        let k = 0;
        if (~(i = 4321)) {
            do Output.printString("FAIL: i should be 4321, is ");
            do Output.printInt(i);
            do Output.println();
            let k = 1;
        }
        if (~(j = 5432)) {
            do Output.printString("FAIL: j should be 5432, is ");
            do Output.printInt(j);
            do Output.println();
            let k = 1;
        }
        if (k = 0) {
            do Output.printString("Test OK");
        }
        return;
    }

    function void test1() {
        var int i;      // function with vars starts with command
        return;
    }

    function void test2() {
        return;         // function without vars starts with command
    }

    function void test3(int i) {
        i = 0;          // function without vars starts with assignment
        return;
    }
}

	
	
	
	
Reply | Threaded
Open this post in threaded view
|

Re: Making "let" optional in the Jack Compiler

tungsten
Cool =)
The same approach can also be used to make the do keyword optional. All that's needed is a slight tweak to use lookahead in order to distinguish between an assignment and a call.
Reply | Threaded
Open this post in threaded view
|

Re: Making "let" optional in the Jack Compiler

cadet1620
Administrator
In reply to this post by cadet1620

Syntax changes including optional "do"

The book's syntax for let and do statements is:

statement:  letStatement | ifStatement | whileStatement | doStatement | returnStatement
letStatement: 'let' varName ( '[' expression ']' )? '=' expression ';'
doStatement: 'do' subroutineCall ';'
subroutineCall : subroutineName '(' expressionList ')' | ( className | varName) '.' subroutineName '(' expressionList ')'

The new syntax is:.

statement:  assignment ';' | subroutineCall ';' | letStatement | ifStatement | whileStatement | doStatement | returnStatement
letStatement: 'let' assignment ';'
assignment: varName ( '[' expression ']' )? '=' expression

Note that the second token in both assignment and subroutineCall is a symbol. Examining this symbol is the lookahead that tungsten mentioned.