[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;
}
}
|
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.
|
Administrator
|
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.
|