.. Jack Analyzer!
CommandType.java
ArithmeticCommand.java
Parser.java
CodeWriter.java
VMTranslator.java
Symbol.java
TokenType.java
TokenTypeDecider.java
JackToken.java
CommentRemover.java
JackTokenizer.java
CompilationEngine.java
JackAnalyzer.java
VM Translator
MemorySegment.javapublic enum MemorySegment { ARGUMENT, LOCAL, STATIC, CONSTANT, THIS, THAT, POINTER, TEMP; public static MemorySegment fromSegment(String segment) { switch (segment.toUpperCase()) { case "ARGUMENT": return ARGUMENT; case "LOCAL": return LOCAL; case "STATIC": return STATIC; case "CONSTANT": return CONSTANT; case "THIS": return THIS; case "THAT": return THAT; case "POINTER": return POINTER; case "TEMP": return TEMP; } return null; } }
CommandType.java
public enum CommandType { C_ARITHMETIC, C_PUSH, C_POP, C_LABEL, C_GOTO, C_IF_GOTO, C_FUNCTION, C_RETURN, C_CALL, IGNORED; public static CommandType fromString(String command) { final ArithmeticCommand arithmeticCommand = ArithmeticCommand.fromCommand(command); if (arithmeticCommand != null) { return C_ARITHMETIC; } switch (command.toUpperCase()) { case "PUSH": return C_PUSH; case "POP": return C_POP; case "LABEL": return C_LABEL; case "GOTO": return C_GOTO; case "IF-GOTO": return C_IF_GOTO; case "FUNCTION": return C_FUNCTION; case "CALL": return C_CALL; case "RETURN": return C_RETURN; } return null; } }
ArithmeticCommand.java
public enum ArithmeticCommand { ADD, SUB, NEG, EQ, GT, LT, AND, OR, NOT; public static ArithmeticCommand fromCommand(String arithmeticCommand) { switch (arithmeticCommand.toUpperCase()) { case "ADD": return ADD; case "SUB": return SUB; case "NEG": return NEG; case "EQ": return EQ; case "GT": return GT; case "LT": return LT; case "AND": return AND; case "OR": return OR; case "NOT": return NOT; } return null; } }
Parser.java
import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; public class Parser { private final Scanner fileScanner; private String currentCommand; private CommandType currentCommandType; // Opens the input file / stream public Parser(File sourceFile) throws FileNotFoundException { fileScanner = new Scanner(sourceFile); } public boolean hasMoreCommands() { return fileScanner.hasNext(); } // Reads the next command from the input and makes it the current // command. public void advance() { currentCommand = fileScanner.nextLine().trim(); } // Returns a constant representing the type of the current command. // C_Arithmetic is returned for all the arithmetic / logical commands. public CommandType commandType() { if (currentCommand == null || currentCommand.length() == 0) { return CommandType.IGNORED; } if (currentCommand.startsWith("//")) { return CommandType.IGNORED; } final String[] split = currentCommand.split("\\s"); final String command = split[0]; currentCommandType = CommandType.fromString(command); return currentCommandType; } // Returns the first argument of the current command. // In case of C_ARITHMETIC the command itself is returned. public String arg1() { final String[] split = currentCommand.split("\\s"); if (currentCommandType == CommandType.C_ARITHMETIC) { return split[0]; } else { return split[1]; } } // Returns the second argument of the current command. public String arg2() { final String[] split = currentCommand.split("\\s"); return split[2]; } }
CodeWriter.java
import java.io.File; import java.io.IOException; import java.io.PrintWriter; public class CodeWriter { final PrintWriter pw; private String sourceFileName; // Opens the output sourceFile / stream and gets ready to write into it. public CodeWriter(final File targetFile) throws IOException { this.pw = new PrintWriter(targetFile); } // Writes the output sourceFile the assembly code that implements the given // arithmetic command. public void writeArithmetic(ArithmeticCommand arithmeticCommand) throws IOException { switch (arithmeticCommand) { case ADD: writeAdd(); break; case SUB: writeSub(); break; case NEG: writeNeg(); break; case EQ: writeEQ(); break; case GT: writeGT(); break; case LT: writeLT(); break; case AND: writeAnd(); break; case OR: writeOr(); break; case NOT: writeNot(); break; } } // Writes the output sourceFile the assembly code that implements the given command, // where command is either C_PUSH or C_POP. public void writePushPop(CommandType commandType, MemorySegment memorySegment, int index) { switch (commandType) { case C_PUSH: writePush(memorySegment, index); break; case C_POP: writePop(memorySegment, index); break; } } public void writeInit() { pw.println("@256"); pw.println("D = A"); pw.println("@SP"); pw.println("M = D"); writeCall("Sys.init", 0); } public void writeLabel(String label) { pw.println("(" + label + ")"); } public void writeGoto(String label) { unCondJumpToLabel(label); } // pop the stacks top most value // if the value is not zero, jump to label // else do not jump.. public void writeIfGoto(String label) { decrementStackPointer(); popStackValueToRegisterD(); pw.println("@" + label); pw.println("D;JNE"); } public void writeFunction(String functionName, int numLocals) { writeLabel(functionName); // initialise local variables to 0.. for (int i = 0; i < numLocals; i++) { loadStackAddressToRegisterA(); pw.println("M = 0"); incrementStackPointer(); } } // R[13 - 15] can be used as general purpose registers by the VM public void writeCall(String functionName, int numArgs) { final String returnAddress = String.valueOf(System.currentTimeMillis()); try { Thread.sleep(2); } catch (InterruptedException ignored) {} // Push return address pw.println("@" + returnAddress); pw.println("D = A"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); // Push LCL pw.println("@LCL"); pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); // Push ARG pw.println("@ARG"); pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); // Push THIS pw.println("@THIS"); pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); // Push THAT pw.println("@THAT"); pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); // ARG = SP - n - 5 pw.println("@SP"); pw.println("D = M"); pw.println("@" + numArgs); pw.println("D = D - A"); pw.println("@5"); pw.println("D = D - A"); pw.println("@ARG"); pw.println("M = D"); // LCL = SP ie. reposition LCL pw.println("@SP"); pw.println("D = M"); pw.println("@LCL"); pw.println("M = D"); // goto functionName writeGoto(functionName); writeLabel(returnAddress); } // R[13 - 15] can be used as general purpose registers by the VM public void writeReturn() { // FRAME is a temporary variable // FRAME == R13 pw.println("@LCL"); pw.println("D = M"); pw.println("@R13"); pw.println("M = D"); // Put the return address in a temp variable (R14) // (RETADDR == *(FRAME - 5)) = (RETADDR == R14) pw.println("@R13"); pw.println("D = M"); pw.println("@5"); pw.println("D = D - A"); pw.println("A = D"); pw.println("D = M"); pw.println("@R14"); pw.println("M = D"); // Copy return value to D pw.println("@SP"); pw.println("A = M"); pw.println("A = A - 1"); pw.println("D = M"); pw.println("@ARG"); pw.println("A = M"); pw.println("M = D"); // Restore Stack Pointer of the caller // SP = ARG + 1 pw.println("@ARG"); pw.println("D = M"); pw.println("D = D + 1"); pw.println("@SP"); pw.println("M = D"); // THAT = *(FRAME - 1) pw.println("@R13"); pw.println("D = M"); pw.println("@1"); pw.println("D = D - A"); pw.println("A = D"); pw.println("D = M"); pw.println("@THAT"); pw.println("M = D"); // THIS = *(FRAME - 2) pw.println("@R13"); pw.println("D = M"); pw.println("@2"); pw.println("D = D - A"); pw.println("A = D"); pw.println("D = M"); pw.println("@THIS"); pw.println("M = D"); // ARG = *(FRAME - 3) pw.println("@R13"); pw.println("D = M"); pw.println("@3"); pw.println("D = D - A"); pw.println("A = D"); pw.println("D = M"); pw.println("@ARG"); pw.println("M = D"); // LCL = *(FRAME - 4) pw.println("@R13"); pw.println("D = M"); pw.println("@4"); pw.println("D = D - A"); pw.println("A = D"); pw.println("D = M"); pw.println("@LCL"); pw.println("M = D"); // goto RET pw.println("@R14"); pw.println("A = M"); pw.println("0;JMP"); } public void close() throws IOException { pw.flush(); pw.close(); } private void writeAdd() { decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("D = D + M"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); } private void writeSub() { decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("D = D - M"); pw.println("D = -D"); loadStackAddressToRegisterA(); pw.println("M = D"); incrementStackPointer(); } private void writeNeg() { decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("M = -M"); incrementStackPointer(); } private void writeEQ() { final String uniqueLabel = String.valueOf(System.currentTimeMillis()); try { Thread.sleep(2); } catch (InterruptedException ignored) { } final String uniqueLabelTrue = uniqueLabel + ".true"; final String uniqueLabelFalse = uniqueLabel + ".false"; final String uniqueLabelEnd = uniqueLabel + ".end"; // true : -1 // false: 0 decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("D = D - M"); pw.println("@" + uniqueLabelTrue); pw.println("D;JEQ"); pw.println("@" + uniqueLabelFalse); pw.println("D;JNE"); pw.println("(" + uniqueLabelTrue + ")"); loadStackAddressToRegisterA(); setSPToTrue(); unCondJumpToLabel(uniqueLabelEnd); pw.println("(" + uniqueLabelFalse + ")"); loadStackAddressToRegisterA(); pw.println("M = 0"); unCondJumpToLabel(uniqueLabelEnd); pw.println("(" + uniqueLabelEnd + ")"); incrementStackPointer(); } private void writeGT() { final String uniqueLabel = String.valueOf(System.currentTimeMillis()); try { Thread.sleep(2); } catch (InterruptedException ignored) { } final String uniqueLabelTrue = uniqueLabel + ".true"; final String uniqueLabelFalse = uniqueLabel + ".false"; final String uniqueLabelEnd = uniqueLabel + ".end"; // true : -1 // false: 0 decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("D = M - D"); pw.println("@" + uniqueLabelTrue); pw.println("D;JGT"); pw.println("@" + uniqueLabelFalse); pw.println("D;JLE"); pw.println("(" + uniqueLabelTrue + ")"); loadStackAddressToRegisterA(); setSPToTrue(); unCondJumpToLabel(uniqueLabelEnd); pw.println("(" + uniqueLabelFalse + ")"); loadStackAddressToRegisterA(); pw.println("M = 0"); unCondJumpToLabel(uniqueLabelEnd); pw.println("(" + uniqueLabelEnd + ")"); incrementStackPointer(); } private void writeLT() { final String uniqueLabel = String.valueOf(System.currentTimeMillis()); try { Thread.sleep(2); } catch (InterruptedException ignored) { } final String uniqueLabelTrue = uniqueLabel + ".true"; final String uniqueLabelFalse = uniqueLabel + ".false"; final String uniqueLabelEnd = uniqueLabel + ".end"; // true : -1 // false: 0 decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("D = M - D"); pw.println("@" + uniqueLabelTrue); pw.println("D;JLT"); pw.println("@" + uniqueLabelFalse); pw.println("D;JGE"); pw.println("(" + uniqueLabelTrue + ")"); loadStackAddressToRegisterA(); setSPToTrue(); unCondJumpToLabel(uniqueLabelEnd); pw.println("(" + uniqueLabelFalse + ")"); loadStackAddressToRegisterA(); pw.println("M = 0"); // set *SP to false unCondJumpToLabel(uniqueLabelEnd); pw.println("(" + uniqueLabelEnd + ")"); incrementStackPointer(); } private void writeAnd() { decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("M = D & M"); incrementStackPointer(); } private void writeOr() { decrementStackPointer(); popStackValueToRegisterD(); decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("M = D | M"); incrementStackPointer(); } private void writeNot() { decrementStackPointer(); loadStackAddressToRegisterA(); pw.println("M = !M"); incrementStackPointer(); } private void writePush(MemorySegment memorySegment, int index) { switch (memorySegment) { case CONSTANT: pw.println("@" + index); pw.println("D = A"); loadStackAddressToRegisterA(); pw.println("M = D"); break; case LOCAL: writePushForMemoryLocation("@LCL", index); break; case ARGUMENT: writePushForMemoryLocation("@ARG", index); break; case THIS: writePushForMemoryLocation("@THIS", index); break; case THAT: writePushForMemoryLocation("@THAT", index); break; case STATIC: pw.println("@" + sourceFileName + "." + index); pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); break; case POINTER: // push THIS or THAT pointer (pointer itself) to stack. if (index == 0) { pw.println("@THIS"); } if (index == 1) { pw.println("@THAT"); } pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); break; case TEMP: // TEMP is not a pointer, general purpose data register. // TEMP is between R5 - R12 pw.println("@" + (5 + index)); pw.println("D = M"); loadStackAddressToRegisterA(); pw.println("M = D"); break; } incrementStackPointer(); } private void writePop(MemorySegment memorySegment, int index) { decrementStackPointer(); switch (memorySegment) { case LOCAL: writePopForMemoryLocation(index, "@LCL"); break; case ARGUMENT: writePopForMemoryLocation(index, "@ARG"); break; case THIS: writePopForMemoryLocation(index, "@THIS"); break; case THAT: writePopForMemoryLocation(index, "@THAT"); break; case STATIC: popStackValueToRegisterD(); pw.println("@" + sourceFileName + "." + index); pw.println("M = D"); break; case POINTER: popStackValueToRegisterD(); if (index == 0) { pw.println("@THIS"); } if (index == 1) { pw.println("@THAT"); } pw.println("M = D"); break; case TEMP: popStackValueToRegisterD(); pw.println("@" + (index + 5)); pw.println("M = D"); break; } } private void writePushForMemoryLocation(String memoryLocation, int index) { // Increment memory location base address by index. pw.println(memoryLocation); pw.println("D = M"); pw.println("@" + index); pw.println("D = D + A"); pw.println(memoryLocation); pw.println("M = D"); // D = *memoryLocation pw.println(memoryLocation); pw.println("A = M"); pw.println("D = M"); // *SP = D loadStackAddressToRegisterA(); pw.println("M = D"); // Reset memory location base address. pw.println(memoryLocation); pw.println("D = M"); pw.println("@" + index); pw.println("D = D - A"); pw.println(memoryLocation); pw.println("M = D"); } private void writePopForMemoryLocation(int index, String memoryLocation) { pw.println(memoryLocation); pw.println("D = M"); pw.println("@" + index); pw.println("D = D + A"); pw.println(memoryLocation); pw.println("M = D"); popStackValueToRegisterD(); pw.println(memoryLocation); pw.println("A = M"); pw.println("M = D"); pw.println(memoryLocation); pw.println("D = M"); pw.println("@" + index); pw.println("D = D - A"); pw.println(memoryLocation); pw.println("M = D"); } private void setSPToTrue() { pw.println("M = 1"); pw.println("M = -M"); // set *SP to true } private void unCondJumpToLabel(String uniqueLabelEnd) { pw.println("@" + uniqueLabelEnd); pw.println("0;JMP"); } private void incrementStackPointer() { pw.println("@SP"); pw.println("M = M + 1"); } private void decrementStackPointer() { pw.println("@SP"); pw.println("M = M - 1"); } private void popStackValueToRegisterD() { loadStackAddressToRegisterA(); pw.println("D = M"); } private void loadStackAddressToRegisterA() { pw.println("@SP"); pw.println("A = M"); } public void setSourceFileName(final String sourceFileName) { this.sourceFileName = sourceFileName; } }
VMTranslator.java
import java.io.File; import java.io.IOException; public class VMTranslator { public static void main(String[] args) throws IOException { String source = args[0]; if (source.endsWith(".vm")) { source = source.substring(0, source.lastIndexOf('.')); } final File initialSourceFile = new File(source); final File targetFile; final boolean isInitialSourceFileDirectory = initialSourceFile.isDirectory(); final File[] sourceFiles; if (isInitialSourceFileDirectory) { targetFile = new File(source + "/" + source + ".asm"); sourceFiles = initialSourceFile.listFiles(); } else { targetFile = new File(source + ".asm"); sourceFiles = new File[]{new File(source + ".vm")}; } final CodeWriter codeWriter = new CodeWriter(targetFile); if (isInitialSourceFileDirectory) { codeWriter.writeInit(); } //noinspection ConstantConditions for (File sourceFile : sourceFiles) { if (!sourceFile.getName().endsWith(".vm")) { continue; } String sourceFileName = sourceFile.getName(); codeWriter.setSourceFileName(sourceFileName); final Parser parser = new Parser(sourceFile); while (parser.hasMoreCommands()) { parser.advance(); final CommandType commandType = parser.commandType(); switch (commandType) { case C_ARITHMETIC: final String command = parser.arg1(); final ArithmeticCommand arithmeticCommand = ArithmeticCommand.fromCommand(command); codeWriter.writeArithmetic(arithmeticCommand); break; case C_PUSH: case C_POP: final String segment = parser.arg1(); MemorySegment memorySegment = MemorySegment.fromSegment(segment); codeWriter.writePushPop(commandType, memorySegment, Integer.valueOf(parser.arg2())); break; case C_LABEL: codeWriter.writeLabel(parser.arg1()); break; case C_GOTO: codeWriter.writeGoto(parser.arg1()); break; case C_IF_GOTO: codeWriter.writeIfGoto(parser.arg1()); break; case C_FUNCTION: codeWriter.writeFunction(parser.arg1(), Integer.parseInt(parser.arg2())); break; case C_CALL: codeWriter.writeCall(parser.arg1(), Integer.parseInt(parser.arg2())); break; case C_RETURN: codeWriter.writeReturn(); break; case IGNORED: // do nothing.. break; } } } codeWriter.close(); } }
Jack Analyzer
Keyword.javapackage biz.tugay.jackanalyzer.tokenizer.token; public enum Keyword { CLASS("class"), CONSTRUCTOR("constructor"), FUNCTION("function"), METHOD("method"), FIELD("field"), STATIC("static"), VAR("var"), INT("int"), CHAR("char"), BOOLEAN("boolean"), VOID("void"), TRUE("true"), FALSE("false"), NULL("null"), THIS("this"), LET("let"), DO("do"), IF("if"), ELSE("else"), WHILE("while"), RETURN("return"); private String jackKeyword; Keyword(String jackKeyword) { this.jackKeyword = jackKeyword; } public String getJackKeyword() { return jackKeyword; } public static boolean isJackKeyword(String token) { final Keyword[] keywords = Keyword.values(); for (Keyword keyword : keywords) { if (keyword.getJackKeyword().equals(token)) { return true; } } return false; } }
Symbol.java
package biz.tugay.jackanalyzer.tokenizer.token; public enum Symbol { BRACES_L('{'), BRACES_R('}'), PARANTHESIS_L('('), PARANTHESIS_R(')'), BRACKETS_L('['), BRACKETS_R(']'), DOT('.'), COMMA(','), SEMI_COLON(';'), PLUS('+'), MINUS('-'), MULT('*'), DIV('/'), AND('&'), OR('|'), LESSER('<'), GREATER('>'), EQUALS('='), HYPHEN('~'); private char jackSymbol; Symbol(char jackSymbol) { this.jackSymbol = jackSymbol; } public char getJackSymbol() { return jackSymbol; } public static boolean isJackSymbol(char token) { final Symbol[] symbols = Symbol.values(); for (Symbol symbol : symbols) { if (symbol.getJackSymbol() == token) { return true; } } return false; } public static Symbol getJackSymbolFor(char token) { final Symbol[] symbols = Symbol.values(); for (Symbol symbol : symbols) { if (symbol.getJackSymbol() == token) { return symbol; } } return null; } public static String getSymbolRepresentationFor(Symbol symbol) { switch (symbol) { case LESSER: return "<"; case GREATER: return ">"; case AND: return "&"; default: final char jackSymbol = symbol.getJackSymbol(); return new String(new char[]{jackSymbol}); } } }
TokenType.java
package biz.tugay.jackanalyzer.tokenizer.token; public enum TokenType { KEYWORD("keyword"), SYMBOL("symbol"), IDENTIFIER("identifier"), INTEGERCONSTANT("integerConstant"), STRINGCONSTANT("stringConstant"); private final String tokenAsString; TokenType(String tokenAsString) { this.tokenAsString = tokenAsString; } public String getTokenAsString() { return tokenAsString; } }
TokenTypeDecider.java
package biz.tugay.jackanalyzer.tokenizer.token; public class TokenTypeDecider { public TokenType tokenTypeFor(String token) { try { //noinspection ResultOfMethodCallIgnored Integer.parseInt(token); return TokenType.INTEGERCONSTANT; } catch (NumberFormatException ignored) { } if (token.startsWith("\"")) { return TokenType.STRINGCONSTANT; } if (token.length() == 1) { char tokenChar = token.charAt(0); if (Symbol.isJackSymbol(tokenChar)) { return TokenType.SYMBOL; } } if (Keyword.isJackKeyword(token)) { return TokenType.KEYWORD; } return TokenType.IDENTIFIER; } }
JackToken.java
package biz.tugay.jackanalyzer.tokenizer; import biz.tugay.jackanalyzer.tokenizer.token.TokenType; public class JackToken { private TokenType tokenType; private String tokenValue; public void setTokenType(TokenType tokenType) { this.tokenType = tokenType; } public TokenType getTokenType() { return tokenType; } public void setTokenValue(String tokenValue) { this.tokenValue = tokenValue; } public String getTokenValue() { return tokenValue; } }
CommentRemover.java
package biz.tugay.jackanalyzer.tokenizer; public class CommentRemover { private boolean inString = false; private boolean inSingleLineComment = false; private boolean inMultiLineComment = false; public char[] removeComments(char[] jackFileContents) { int mark = 0; char[] jackFileContentsCommentsRemoved = new char[jackFileContents.length]; for (int i = 0; i < jackFileContents.length; i++) { char currentChar = jackFileContents[i]; if (inSingleLineComment) { if (i == jackFileContents.length - 1 || currentChar == '\n') { inSingleLineComment = false; } continue; } if (inMultiLineComment) { if (currentChar == '/' && jackFileContents[i - 1] == '*') { inMultiLineComment = false; } continue; } if (currentChar == '/' && jackFileContents[i + 1] == '/') { inSingleLineComment = true; continue; } if (currentChar == '/' && jackFileContents[i + 1] == '*') { inMultiLineComment = true; continue; } if (jackFileContents[i] == '\"') { switchInString(); } if (inString) { jackFileContentsCommentsRemoved[mark] = currentChar; mark++; continue; } if (currentChar == '\n' || currentChar == ' ' || currentChar == '\t') { currentChar = ' '; if (mark != 0 && jackFileContentsCommentsRemoved[mark - 1] == ' ') { continue; } else { jackFileContentsCommentsRemoved[mark] = currentChar; } } else { jackFileContentsCommentsRemoved[mark] = currentChar; } mark++; } int length = mark; int srcPos = 0; if (jackFileContentsCommentsRemoved[0] == ' ') { srcPos = 1; length--; } if (jackFileContentsCommentsRemoved[mark - 1] == ' ') { length--; } char[] trimmedArray = new char[length]; System.arraycopy(jackFileContentsCommentsRemoved, srcPos, trimmedArray, 0, length); return trimmedArray; } private void switchInString() { inString = !inString; } }
JackTokenizer.java
package biz.tugay.jackanalyzer.tokenizer; import biz.tugay.jackanalyzer.tokenizer.token.Symbol; import biz.tugay.jackanalyzer.tokenizer.token.TokenType; import biz.tugay.jackanalyzer.tokenizer.token.TokenTypeDecider; // Removes all comments and white space from the input stream and // breaks it into Jack-language tokens, as specified by the Jack grammar. public class JackTokenizer { private String currentToken; private char[] jackFileContents; private int pos = 0; public boolean hasMoreTokens() { if (pos >= jackFileContents.length) { return false; } return true; } // Gets the next token from the input and makes it the current token. public void advance() { final StringBuilder stringBuilder = new StringBuilder(); if (jackFileContents[pos] == ' ') { pos++; } if (jackFileContents[pos] == '\"') { stringBuilder.append(jackFileContents[pos]); pos++; while (jackFileContents[pos] != '\"') { stringBuilder.append(jackFileContents[pos]); pos++; } stringBuilder.append(jackFileContents[pos]); pos++; currentToken = stringBuilder.toString(); return; } if (Symbol.isJackSymbol(jackFileContents[pos])) { stringBuilder.append(jackFileContents[pos]); currentToken = stringBuilder.toString(); pos++; return; } boolean tokenComplete = false; while (!tokenComplete) { if (pos == jackFileContents.length || jackFileContents[pos] == ' ' || Symbol.isJackSymbol(jackFileContents[pos])) { tokenComplete = true; } else { stringBuilder.append(jackFileContents[pos]); pos++; } } this.currentToken = stringBuilder.toString(); } public TokenType tokenType() { final TokenTypeDecider tokenTypeDecider = new TokenTypeDecider(); final TokenType tokenType = tokenTypeDecider.tokenTypeFor(currentToken); return tokenType; } public String keyword() { return currentToken; } public String symbol() { final Symbol jackSymbol = Symbol.getJackSymbolFor(currentToken.charAt(0)); return Symbol.getSymbolRepresentationFor(jackSymbol); } public String identifier() { return currentToken; } public String intVal() { return currentToken; } public String stringVal() { return currentToken.substring(1, currentToken.length() - 1); } public void setJackFileContents(char[] jackFileContents) { this.jackFileContents = jackFileContents; } private CommentRemover commentRemover; public void setCommentRemover(CommentRemover commentRemover) { this.commentRemover = commentRemover; } public void removeAllComments() { jackFileContents = commentRemover.removeComments(jackFileContents); } public String getCurrentToken() { return currentToken; } }
CompilationEngine.java
package biz.tugay.jackanalyzer.compilation; import biz.tugay.jackanalyzer.tokenizer.JackToken; import biz.tugay.jackanalyzer.tokenizer.token.TokenType; import java.util.*; // Print and advance! public class CompilationEngine { private JackToken[] jackTokens; int pos = 0; public JackToken currentJackToken; private final StringBuilder stringBuilder = new StringBuilder(); public String compileClass() { currentJackToken = jackTokens[pos]; stringBuilder.append("<class>"); stringBuilder.append("\n"); // <keyword> class </keyword> printCurrentToken(); advanceCurrentJackToken(); // <identifier> main </identifier> printCurrentToken(); advanceCurrentJackToken(); // <symbol> { </symbol> printCurrentToken(); advanceCurrentJackToken(); compileClassVarDeclarations(); compileSubRoutineDeclarations(); // <symbol> } </symbol> printCurrentToken(); // no more advance!!! stringBuilder.append("</class>"); stringBuilder.append("\n"); return stringBuilder.toString(); } private void compileClassVarDeclarations() { while (!isSubRoutineDeclarationStart(currentJackToken.getTokenValue())) { if (currentJackToken.getTokenValue().equals("}")) { break; } compileClassVarDeclaration(); } } private void compileClassVarDeclaration() { stringBuilder.append("<classVarDec>"); stringBuilder.append("\n"); while (!currentJackToken.getTokenValue().equals(";")) { printCurrentToken(); advanceCurrentJackToken(); } printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</classVarDec>"); stringBuilder.append("\n"); } private void compileSubRoutineDeclarations() { while (isSubRoutineDeclarationStart(currentJackToken.getTokenValue())) { compileSubRoutineDeclaration(); } } private void compileSubRoutineDeclaration() { stringBuilder.append("<subroutineDec>"); stringBuilder.append("\n"); // <keyword> function | method | constructor </keyword> printCurrentToken(); advanceCurrentJackToken(); // (<keyword> void | int | char | boolean </keyword>) | (<identifier> Foo </identifier>) printCurrentToken(); advanceCurrentJackToken(); // <identifier> main </identifier> printCurrentToken(); advanceCurrentJackToken(); // <symbol> ( </symbol> printCurrentToken(); advanceCurrentJackToken(); compileParameterList(); // parameter list.. // <symbol> ) </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSubRoutineBody(); stringBuilder.append("</subroutineDec>"); stringBuilder.append("\n"); } private void compileParameterList() { stringBuilder.append("<parameterList>"); stringBuilder.append("\n"); while (!currentJackToken.getTokenValue().equals(")")) { printCurrentToken(); advanceCurrentJackToken(); } stringBuilder.append("</parameterList>"); stringBuilder.append("\n"); } private void compileSubRoutineBody() { stringBuilder.append("<subroutineBody>"); stringBuilder.append("\n"); // <symbol> { </symbol> printCurrentToken(); advanceCurrentJackToken(); compileVarDecs(); compileStatements(); // <symbol> } </symbol> printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</subroutineBody>"); stringBuilder.append("\n"); } private void compileVarDecs() { while (currentJackToken.getTokenValue().equals("var")) { stringBuilder.append("<varDec>"); stringBuilder.append("\n"); // <keyword> var </keyword> printCurrentToken(); advanceCurrentJackToken(); // <keyword> int </keyword> printCurrentToken(); advanceCurrentJackToken(); // <identifier> foo </identifier> printCurrentToken(); advanceCurrentJackToken(); while (currentJackToken.getTokenValue().equals(",")) { // <identifier> , </identifier> printCurrentToken(); advanceCurrentJackToken(); // <identifier> bar </identifier> printCurrentToken(); advanceCurrentJackToken(); } // <symbol> ; </symbol> printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</varDec>"); stringBuilder.append("\n"); } } private void compileStatements() { stringBuilder.append("<statements>"); stringBuilder.append("\n"); while (isStatementStart(currentJackToken.getTokenValue())) { final String tokenValue = currentJackToken.getTokenValue(); if (tokenValue.equals("let")) { compileLetStatement(); } if (tokenValue.equals("return")) { compileReturnStatement(); } if (tokenValue.equals("if")) { compileIfStatement(); } if (tokenValue.equals("while")) { compileWhileStatement(); } if (tokenValue.equals("do")) { stringBuilder.append("<doStatement>"); stringBuilder.append("\n"); printCurrentToken(); advanceCurrentJackToken(); compileSubRoutineCall(); printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</doStatement>"); stringBuilder.append("\n"); } } stringBuilder.append("</statements>"); stringBuilder.append("\n"); } private void compileWhileStatement() { stringBuilder.append("<whileStatement>"); stringBuilder.append("\n"); // <keyword> while </keyword> printCurrentToken(); advanceCurrentJackToken(); // <symbol> ( </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); // <symbol> ) </symbol> printCurrentToken(); advanceCurrentJackToken(); // <symbol> { </symbol> printCurrentToken(); advanceCurrentJackToken(); compileStatements(); // <symbol> } </symbol> printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</whileStatement>"); stringBuilder.append("\n"); } private void compileIfStatement() { stringBuilder.append("<ifStatement>"); stringBuilder.append("\n"); // <keyword> if </keyword> printCurrentToken(); advanceCurrentJackToken(); // <symbol> ( </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); // <symbol> ) </symbol> printCurrentToken(); advanceCurrentJackToken(); // <symbol> { </symbol> printCurrentToken(); advanceCurrentJackToken(); compileStatements(); // <symbol> } </symbol> printCurrentToken(); advanceCurrentJackToken(); if (currentJackToken.getTokenValue().equals("else")) { // <identifier> else </identifier> printCurrentToken(); advanceCurrentJackToken(); // <symbol> { </symbol> printCurrentToken(); advanceCurrentJackToken(); compileStatements(); // <symbol> } </symbol> printCurrentToken(); advanceCurrentJackToken(); } stringBuilder.append("</ifStatement>"); stringBuilder.append("\n"); } private void compileLetStatement() { stringBuilder.append("<letStatement>"); stringBuilder.append("\n"); // <keyword> let </keyword> printCurrentToken(); advanceCurrentJackToken(); // <identifier> a </identifier> printCurrentToken(); advanceCurrentJackToken(); if (currentJackToken.getTokenValue().equals("[")) { // <symbol> [ </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); // <symbol> ] </symbol> printCurrentToken(); advanceCurrentJackToken(); } // <symbol> = </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); // <symbol> ; </symbol> printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</letStatement>"); stringBuilder.append("\n"); } private void compileReturnStatement() { stringBuilder.append("<returnStatement>"); stringBuilder.append("\n"); printCurrentToken(); // <keyword> return </keyword> advanceCurrentJackToken(); if (!currentJackToken.getTokenValue().equals(";")) { compileSingleExpression(); } // <symbol> ; </symbol> printCurrentToken(); advanceCurrentJackToken(); stringBuilder.append("</returnStatement>"); stringBuilder.append("\n"); } private void compileSingleExpression() { stringBuilder.append("<expression>"); stringBuilder.append("\n"); compileTerm(); while (isOp(currentJackToken.getTokenValue())) { printCurrentToken(); advanceCurrentJackToken(); compileTerm(); } stringBuilder.append("</expression>"); stringBuilder.append("\n"); } private void compileTerm() { stringBuilder.append("<term>"); stringBuilder.append("\n"); if (currentJackToken.getTokenType() == TokenType.INTEGERCONSTANT) { compileIntegerConstant(); } else if (currentJackToken.getTokenType() == TokenType.STRINGCONSTANT) { compileStringConstant(); } else if (currentJackToken.getTokenType() == TokenType.KEYWORD) { compileKeywordConstant(); } else if (currentJackToken.getTokenValue().equals("(")) { // <symbol> ( </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); // <symbol> ) </symbol> printCurrentToken(); advanceCurrentJackToken(); } else if (isUnaryOp(currentJackToken.getTokenValue())) { // <symbol> ~ </symbol> printCurrentToken(); advanceCurrentJackToken(); compileTerm(); } else if (jackTokens[pos + 1].getTokenValue().equals("(") || jackTokens[pos + 1].getTokenValue().equals(".")) { compileSubRoutineCall(); } else { compileIdentifier(); if (jackTokens[pos].getTokenValue().equals("[")) { // <symbol> [ </symbol> printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); // <symbol> ] </symbol> printCurrentToken(); advanceCurrentJackToken(); } } stringBuilder.append("</term>"); stringBuilder.append("\n"); } private void compileIdentifier() { // <identifier> foo </identifier> printCurrentToken(); advanceCurrentJackToken(); } private void compileSubRoutineCall() { // <identifier> Keyboard </identifier> printCurrentToken(); advanceCurrentJackToken(); if (currentJackToken.getTokenValue().equals("(")) { // <symbol> ( </symbol> printCurrentToken(); advanceCurrentJackToken(); compileExpressionList(); // <symbol> ) </symbol> printCurrentToken(); advanceCurrentJackToken(); } else { // <symbol> . </symbol> printCurrentToken(); advanceCurrentJackToken(); // subRoutineName.. printCurrentToken(); advanceCurrentJackToken(); // <symbol> ( </symbol> printCurrentToken(); advanceCurrentJackToken(); compileExpressionList(); // <symbol> ) </symbol> printCurrentToken(); advanceCurrentJackToken(); } } private void compileExpressionList() { stringBuilder.append("<expressionList>"); stringBuilder.append("\n"); if (!currentJackToken.getTokenValue().equals(")")) { compileSingleExpression(); while (currentJackToken.getTokenValue().equals(",")) { printCurrentToken(); advanceCurrentJackToken(); compileSingleExpression(); } } stringBuilder.append("</expressionList>"); stringBuilder.append("\n"); } private void compileKeywordConstant() { // <keyword> true </keyword> printCurrentToken(); advanceCurrentJackToken(); } private void compileIntegerConstant() { // <integerConstant> 20 </integerConstant> printCurrentToken(); advanceCurrentJackToken(); } private void compileStringConstant() { // <stringConstant> hello </string printCurrentToken(); advanceCurrentJackToken(); } private void printCurrentToken() { stringBuilder.append("<").append(currentJackToken.getTokenType().getTokenAsString()).append(">").append(currentJackToken.getTokenValue()).append("</").append(currentJackToken.getTokenType().getTokenAsString()).append(">"); stringBuilder.append("\n"); } private void advanceCurrentJackToken() { pos++; setCurrentJackToken(); } private void setCurrentJackToken() { currentJackToken = jackTokens[pos]; } public void setJackTokens(final List<JackToken> jackTokens) { this.jackTokens = jackTokens.toArray(new JackToken[0]); } // Helper methods.. private boolean isSubRoutineDeclarationStart(final String currentTokenValue) { final Set<String> validSubRoutineDeclaration = new HashSet<String>() { { add("constructor"); add("function"); add("method"); } }; if (validSubRoutineDeclaration.contains(currentTokenValue)) { return true; } return false; } private boolean isStatementStart(final String currentTokenValue) { final Set<String> statementStarts = new HashSet<String>() { { add("let"); add("if"); add("while"); add("do"); add("return"); } }; if (statementStarts.contains(currentTokenValue)) { return true; } return false; } private boolean isOp(String tokenValue) { final Set<String> ops = new HashSet<String>() { { add("+"); add("-"); add("*"); add("/"); add("&"); add("|"); add("<"); add(">"); add("="); add("<"); add(">"); add("&"); } }; if (ops.contains(tokenValue)) { return true; } return false; } private boolean isUnaryOp(String tokenValue) { final Set<String> ops = new HashSet<String>() { { add("-"); add("~"); } }; if (ops.contains(tokenValue)) { return true; } return false; } }
JackAnalyzer.java
package biz.tugay.jackanalyzer; import biz.tugay.jackanalyzer.compilation.CompilationEngine; import biz.tugay.jackanalyzer.tokenizer.CommentRemover; import biz.tugay.jackanalyzer.tokenizer.JackToken; import biz.tugay.jackanalyzer.tokenizer.JackTokenizer; import biz.tugay.jackanalyzer.tokenizer.token.TokenType; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class JackAnalyzer { public static void main(String[] args) throws IOException { String source = args[0]; if (source.endsWith(".jack")) { source = source.substring(0, source.lastIndexOf('.')); } final File initialSourceFile = new File(source); final boolean isInitialSourceFileDirectory = initialSourceFile.isDirectory(); String dirName = ""; if (isInitialSourceFileDirectory) { dirName = source; } final File[] sourceFiles; if (isInitialSourceFileDirectory) { sourceFiles = initialSourceFile.listFiles(); } else { sourceFiles = new File[]{new File(source + ".jack")}; } assert sourceFiles != null; for (File sourceFile : sourceFiles) { if (!sourceFile.getName().endsWith(".jack")) { continue; } String targetTokenFileName; if (isInitialSourceFileDirectory) { targetTokenFileName = dirName + "/" + sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf('.')) + "T.xml"; } else { targetTokenFileName = source + "T.xml"; } String targetCompilationFileName; if (isInitialSourceFileDirectory) { targetCompilationFileName = dirName + "/" + sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf('.')) + ".xml"; } else { targetCompilationFileName = source + ".xml"; } final File targetTokenFile = new File(targetTokenFileName); final File targetCompilationFile = new File(targetCompilationFileName); char[] charArray = readFileToCharArray(sourceFile); // Setup classes final JackTokenizer jackTokenizer = new JackTokenizer(); final CommentRemover commentRemover = new CommentRemover(); jackTokenizer.setCommentRemover(commentRemover); jackTokenizer.setJackFileContents(charArray); // Clean up the .jack file contents jackTokenizer.removeAllComments(); List<JackToken> jackTokens = new ArrayList<JackToken>(); while (jackTokenizer.hasMoreTokens()) { jackTokenizer.advance(); final TokenType tokenType = jackTokenizer.tokenType(); final JackToken jackToken = new JackToken(); jackToken.setTokenType(tokenType); switch (tokenType) { case IDENTIFIER: jackToken.setTokenValue(jackTokenizer.identifier()); break; case INTEGERCONSTANT: jackToken.setTokenValue(jackTokenizer.intVal()); break; case KEYWORD: jackToken.setTokenValue(jackTokenizer.keyword()); break; case SYMBOL: jackToken.setTokenValue(jackTokenizer.symbol()); break; case STRINGCONSTANT: jackToken.setTokenValue(jackTokenizer.stringVal()); break; } jackTokens.add(jackToken); } final StringBuilder jackTokensStringBuilder = new StringBuilder(); for (JackToken jackToken : jackTokens) { jackTokensStringBuilder.append("<").append(jackToken.getTokenType().getTokenAsString()).append(">").append(jackToken.getTokenValue()).append("</").append(jackToken.getTokenType().getTokenAsString()).append(">"); jackTokensStringBuilder.append("\n"); } final CompilationEngine compilationEngine = new CompilationEngine(); compilationEngine.setJackTokens(jackTokens); final String tokensString = jackTokensStringBuilder.toString(); final String compilationString = compilationEngine.compileClass(); final FileWriter tokenFileWriter = new FileWriter(targetTokenFile); tokenFileWriter.write(tokensString); tokenFileWriter.flush(); tokenFileWriter.close(); final FileWriter compilationFileWriter = new FileWriter(targetCompilationFile); compilationFileWriter.write(compilationString); compilationFileWriter.flush(); compilationFileWriter.close(); } } private static char[] readFileToCharArray(File file) throws FileNotFoundException { String theString; Scanner scanner = new Scanner(file); theString = scanner.nextLine(); while (scanner.hasNextLine()) { theString = theString + "\n" + scanner.nextLine(); } return theString.toCharArray(); } }